JSON.NET custom constructor - c#

as a partial problem of this question JSON.NET CustomCreationConverter with nested objects I tried to call a custom constructor during deserialization. My simplified class hierarchy is as follows:
public abstract class BusinessObjectBase
{
internal BusinessObjectBase(SerializationContext context)
: base(context)
{
}
}
public abstract class EditableObjectBase : BusinessObjectBase
{
protected EditableObjectBase(SerializationContext context)
: base(context)
{
}
}
public class EditableObjectCollection<TObject> : BusinessObjectBase, ICollection<TObject>, IList, INotifyCollectionChanged where TObject : BusinessObjectBase
{
protected EditableObjectCollection(SerializationContext context)
: base(context)
{
}
}
I know the object hierarchy to a certain level, but users are allowed / forced to derive their own classes. My idea was to write a custom creation converter. The problem I'm fighting with is that a property in a serialized object can be declared as BusinessObjectBase which is abstract, but the real object will be a more derived class and might be a collection or not. A CustomCreationConverter only gets this abstract type passed to the Create method and of course can't create the correct type from this information.
Inspired from this How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects I implemented a converter as follows:
internal class BusinessObjectCreationConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return typeof(BusinessObjectBase).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = null;
if (reader.TokenType != JsonToken.Null)
{
JObject jsonObject = JObject.Load(reader);
result = this.Create(objectType, jsonObject);
Verification.Assert<NullReferenceException>(result != null, "No Business Object created.");
serializer.Populate(jsonObject.CreateReader(), result);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public BusinessObjectBase Create(Type objectType, JObject jsonObject)
{
JToken token = jsonObject.SelectToken("$type");
var typeString = token.Value<string>();
Type type = Type.GetType(typeString);
var businessObject = type.CreateUsingDesrializationConstructor<BusinessObjectBase>();
businessObject.Initialize(true);
return businessObject;
}
}
My class to test serialization looks like this:
public class AnyPocoContainingBusinessObject
{
public BusinessObjectBase BusinessObject { get; set; }
}
public class TestEditableObject : EditableObjectBase
{
internal TestEditableObject(SerializationContext context)
: base(context)
{
}
}
If I initialize my class my class with an collection
var collection = new EditableObjectCollection<TestEditableObject>(null);
var poco = new AnyPocoContainingBusinessObject { BusinessObject = collection };
and configure the serializer this way:
public NewtonsoftJsonSerializer()
: this(new JsonSerializer
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
DefaultValueHandling = DefaultValueHandling.Ignore,
ContractResolver = new KsJsonContractResolver()
})
{
this.serializer.Converters.Add(new ReadOnlyObjectCollectionConverter());
this.serializer.Converters.Add(new BusinessObjectCreationConverter());
this.serializer.TraceWriter = new ConsoleTraceWriter();
}
I get an exception:
Cannot populate JSON object onto type 'KS.Interfaces.Core.Entities.EditableObjectCollection`1[KS.Interfaces.Core.Entities.Tests.Unit.EditableObjectCollectionTests+TestEditableObject]'. Path '$type', line 1, position 47.
in this code line of my converter:
serializer.Populate(jsonObject.CreateReader(), result);
Can any body tell me what might be the reason? I'm pretty sure that I created the correct type and with a EditableObjectBase derived object everything is fine. Only collections doesn't seem to work.
Any hints are highly appreciated, thank in advance
Carsten

even I haven't found a way to make my converter work there is one thing I learned during debugging the problem:
It seems that a converter should return an object that still has the same JsonToken value. In my case the JsonToken of the original object was JsonToken.Object, but for my conversion result the correct token value would be JsonToken.Array, but the reader still sees JsonToken.Object. At this point I stopped my research since I found a better way to call my custom constructor.
I wrote my own contract resolver:
internal class BusinessBaseContractResolver : DefaultContractResolver
{
public BusinessBaseContractResolver()
{
this.DefaultMembersSearchFlags |= BindingFlags.NonPublic;
}
public override JsonContract ResolveContract(Type type)
{
JsonContract contract = base.ResolveContract(type);
if (typeof(BusinessObjectBase).IsAssignableFrom(type))
{
contract.DefaultCreator = delegate
{
var businessObject = type.CreateUsingDeserializationConstructor<BusinessObjectBase>();
businessObject.Initialize(true);
return businessObject;
};
}
return contract;
}
}
I hope this helps someone.
Best regards,
Carsten

Related

Newtonsoft.Json - how can I choose the constructor to deserialize based on passed object properties types?

I have a generic type with 2 constructor each accepting 1 parameter with different types.
There is no problem with serialization. But when I try to deserialize, I get an error that Newtonsoft is Unable to find a constructor to use for this type.
How can I override Newtonsoft's default behavious and choose the constructor to be used for deserialization based on the property types?
I DO NOT have access to the Test class, so I can not change it in any way.
I wish to use another way not write a an entirely custom JsonConverter.
I saw this answer here that you can override the DefaultContractResolver. It works well to choose a different constructor, but I can not see how I can access the properties of the object I am trying to deserialize?
How can I choose a constructor based on the properties types of the object to be deserialized?
public class Test<T, U> where U : struct
{
public Test(T firstProperty)
{
FirstProperty = firstProperty;
}
public Test(U secondProperty)
{
SecondProperty = secondProperty;
}
public T FirstProperty { get; }
public U SecondProperty { get; }
}
There is no way to configure Json.NET to choose a constructor based on the presence or absence of certain properties in the JSON to be deserialized. It simply isn't implemented.
As a workaround, you can create a custom JsonConverter<Test<T, U>> that deserializes to some intermediate DTO that tracks the presence of both properties, and then chooses the correct constructor afterwards. Then you can create a custom contract resolver that applies the converter to all concrete types Test<T, U>.
The following converter and contract resolver perform this task:
class TestConverter<T, U> : JsonConverter where U : struct
{
// Here we make use of the {PropertyName}Specified pattern to track which properties actually got deserialized.
// https://stackoverflow.com/questions/39223335/how-to-force-newtonsoft-json-to-serialize-all-properties-strange-behavior-with/
class TestDTO
{
public T FirstProperty { get; set; }
[JsonIgnore] public bool FirstPropertySpecified { get; set; }
public U SecondProperty { get; set; }
[JsonIgnore] public bool SecondPropertySpecified { get; set; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<TestDTO>(reader);
if (dto == null)
return null;
else if (dto.FirstPropertySpecified && !dto.SecondPropertySpecified)
return new Test<T, U>(dto.FirstProperty);
else if (!dto.FirstPropertySpecified && dto.SecondPropertySpecified)
return new Test<T, U>(dto.SecondProperty);
else
throw new InvalidOperationException(string.Format("Wrong number of properties specified for {0}", objectType));
}
public override bool CanConvert(Type objectType) => objectType == typeof(Test<T, U>);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public class TestContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>))
contract.Converter = (JsonConverter)Activator.CreateInstance(typeof(TestConverter<,>).MakeGenericType(objectType.GetGenericArguments()));
return contract;
}
}
Then use them e.g. as follows:
var json1 = #"{""FirstProperty"":""hello""}";
var json2 = #"{""SecondProperty"": 10101}";
IContractResolver resolver = new TestContractResolver(); // Cache this statically for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var test1 = JsonConvert.DeserializeObject<Test<string, long>>(json1, settings);
Assert.AreEqual(test1.FirstProperty, "hello");
var test2 = JsonConvert.DeserializeObject<Test<string, long>>(json2, settings);
Assert.AreEqual(test2.SecondProperty, 10101L);
Notes:
The converter throws an InvalidOperationException if the JSON does not contain exactly one of the two properties.
Feel free to modify this as per your requirements.
The converter does not implement serialization as your Test<T, U> type does not provide a method to track which property was initialized.
The converter does not attempt to handle subclasses of Test<T, U>.
Demo fiddle here.

How can I customize the deserialization of a nested property in Json.net without adding attributes

Suppose I have these classes:
public class Bar
{
public Foo MyFoo { get; set; }
}
public class Foo
{
public string[] Stuff { get; set; }
}
And I have this JSON structure:
{
"MyFoo":
{
"Stuff":"those,are,my,stuff"
}
}
And I have a code path where a JObject is being converted to Bar using code like below:
myJObject.ToObject(typeof(Bar))
Now what I need to do is to supply the ToObject with a custom serializer to convert the string property Stuff into an array of string (using string.Split(...).ToArray())
I was asked not to add attributes on the client class 'Bar' so after looking around it seemed like a ContractResolver is in order but the issue is that the resolver only lets me handle direct properties of the root Type, that is Bar in my example, and I can't register a JsonConverter on a nested property.
So my question to you guys is, is this even achievable using Json.net?
Note that I need to do this not only for the Bar class but to an unlimited amount of classes with unknown structure so I can't hard-code a solution that will work for one type of class.
Based on your description, I don't see why you would need a ContractResolver. You know that the properties you want to deserialize specially will always be of type string[], so just make a converter that handles that type. Maybe something like this:
public class CsvStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Null)
return null;
if (token.Type == JTokenType.String)
return ((string)token).Split(',');
if (token.Type == JTokenType.Array)
return token.ToObject<string[]>(serializer);
throw new JsonException("Unexpected token type: " + token.Type);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, to use it with your JObject, create a new JsonSerializer instance, add the converter to it, and pass the serializer to the ToObject() method like this:
var serializer = new JsonSerializer();
serializer.Converters.Add(new CsvStringConverter());
var bar = myJObject.ToObject<Bar>(serializer);
Working demo here: https://dotnetfiddle.net/qmeBoh

How to serialize a Guid with C# without creating an object?

I want my Guids to be serialized into a short form of Guid (as seen here). For full compatibility, I want the ShortGuid class to serialize into the short form, and not the long form.
I've tried using custom serialization, but I can't seem to make the whole object serialize into the short string, only into an object which contains the string. This is what I've tried:
[Serializable]
public class ShortGuid : ISerializable
{
public Guid Guid { get; }
protected ShortGuid(SerializationInfo info, StreamingContext context)
{
Guid = Decode(info.GetString("Guid"));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Guid", Encode(Guid));
}
}
Which works, but if I serialize it:
var guid = new ShortGuid(Guid.NewGuid());
var str = JsonConvert.Serialize(guid);
The serialized string I get looks like this:
{ Guid: "xxxxxxxxxxxxxxxxxx" }
While the serialized string I WANT is just
xxxxxxxxxxxxxxxxxx
I've turned everything around, but can't get this to work. How can this be done?
NOTE: I don't want this to work only on JsonConvert, this is only an example. I would like the class the always be serialized correctly.
this work perfectly for me:
public class GuidShortGuidConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Guid);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var shortGuid = new ShortGuid(reader.Value.ToString());
return shortGuid.Guid;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var customValue = new ShortGuid((Guid) value);
writer.WriteValue(customValue.ToString());
}
}
What you want is not about serialization but implicit casting.
Serialization works on structured key/value base depending on serialization type.
With implicit string casting
public static implicit operator string(ShortGuid shortGuid)
{
return Encode(sh.Guid);
}
You can use
var guid = new ShortGuid(Guid.NewGuid());
string str = guid;

JsonConverter CanConvert does not receive type

I have a custom JsonConverter, which doesn't seem to be called correctly. I have created the converter, added it to the JsonSerializerSettings.Converters collection and marked the property on the entity I am serialising with [JsonConverter(typeof(SearchGeoConverter))], but even with these in place the converters CanConvert method never sees the type I am trying to convert. I only ever see, string, int and JObject.
My converter looks like this:
public class SearchGeoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DbGeography).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var point = (DbGeography) value;
var rawJson = string.Format("{{ \"type\": \"Point\", \"coordinates\": [{0}, {1}] }}", point.Latitude, point.Longitude);
writer.WriteRaw(rawJson);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What am I missing?
CanConvert does not get called when you mark something with [JsonConverter]. When you use the attribute, Json.Net assumes you have provided the correct converter, so it doesn't bother with the CanConvert check. If you remove the attribute, then it will get called by virtue of you passing the converter instance to the settings. What you are seeing is Json.Net testing your converter for all the other property types.
EDIT
I put together a quick fiddle to show what I mean (code is also reproduced below for completeness).
With no changes the program, CanConvert() gets called on the FooConverter for all types except Foo, yet it still converts Foo correctly.
If you comment out the [JsonConverter] attribute on the Wrapper.Foo property, you can see that CanConvert() will now get called for type Foo by virtue of the FooConverter being included in the JsonSerializerSettings.
If you instead comment out the line in Main where the FooConverter is added to the settings, then CanConvert is never called for any type, yet Foo is still converted correctly due to the [JsonConverter] attribute applied to the Foo property in the Wrapper class.
So the takeaway here is that there are two mechanisms for indicating whether a converter should be used, and you don't need both. You can apply an attribute, and that will tell Json.Net that a particular converter should be used for a particular property (or class) and it does not need to ask the converter first. Alternatively, you can add the converter to the settings, in which case Json.Net has to ask each converter whether it can handle each type. The former is a bit more efficient, while the latter is useful in situations where you don't own the source code for the class you're trying to convert. Hope this makes sense.
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
public class Program
{
public static void Main()
{
JsonSerializerSettings settings = new JsonSerializerSettings();
// Comment out the following line and CanConvert() never gets called on
// FooConverter for any type yet the FooConverter is still working due
// to the JsonConverter attribute applied to Wrapper.Foo
settings.Converters.Add(new FooConverter());
settings.Converters.Add(new BarConverter());
settings.Formatting = Formatting.Indented;
Wrapper w = new Wrapper
{
Foo = new Foo
{
A = "bada",
B = "boom",
},
Bar = new Bar
{
C = "bada",
D = "bing"
}
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
class Wrapper
{
// Comment out this attribute and CanConvert will be called on FooConverter
// for type Foo due to the fact that the FooConverter has been added to the
// JsonSerializerSettings
[JsonConverter(typeof(FooConverter))]
public Foo Foo { get; set; }
public Bar Bar { get; set; }
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
}
class Bar
{
public string C { get; set; }
public string D { get; set; }
}
class FooConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
bool result = typeof(Foo).IsAssignableFrom(objectType);
Console.WriteLine("FooConverter CanConvert() called for type " +
objectType.Name + " (result = " + result + ")");
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var foo = (Foo) value;
JObject jo = new JObject();
jo.Add("AplusB", new JValue(foo.A + " " + foo.B));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
class BarConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
bool result = typeof(Bar).IsAssignableFrom(objectType);
Console.WriteLine("BarConverter CanConvert() called for type " +
objectType.Name + " (result = " + result + ")");
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bar = (Bar) value;
JObject jo = new JObject();
jo.Add("CplusD", new JValue(bar.C + " " + bar.D));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}

Json MissingMemberHandling when using converters

Deserializing a saved interface object using Json.NET without defining it's implementation type was throwing errors (of course) so I wrote a re-usable class:
public class MakeJsonConverter<TParent, TChild> : JsonConverter where TChild:TParent
{
private readonly Type _parent;
private Type _child;
public MakeJsonConverter()
{
_parent = typeof(TParent);
_child = typeof(TChild);
}
public override bool CanConvert(Type objectType)
{
return (objectType == _parent);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<TChild>(reader);
}
//This should never be called. Only reading from saved interface data was an issue without using a converter.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It works fine, except for the fact that the settings I pass to it don't seem to be used. Actually I don't know that for sure, what I do know is that MissingMemberHandling.Error isn't working.
public virtual string SerializeWithConverter<TParent, TChild>(TParent obj) where TChild : TParent
{
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new DefaultContractResolver
{
DefaultMembersSearchFlags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
},
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
//Settings important to this situation
MissingMemberHandling = MissingMemberHandling.Error,
Converters = new List<JsonConverter> { new MakeJsonConverter<TParent, TChild>() }
};
return JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
}
I can't seem to figure out why this is working the way it is.. can anyone explain?
Try adding TypeNameHandling = TypeNameHandling.All when serializing and deserializing
This might help the deserializer in case you have nested objects structures which you want to deserialize
What exactly do you mean when you say MissingMemberHandling.Error isn't working?
I'm assuming it's because you are expecting an error when a user over/under posts an object that doesn't match your object? Either way, I believe the MissingMemberHandling.Error won't throw when you set your own custom ContractResolver. Instead, create your own class that inherits from DefaultContractResolver and override the JsonObjectContract. This will provide a result similar to what you are expecting.
public class CustomContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.ItemRequired = Required.Always;
return contract;
}
}
Then use that as your contract resolver
ContractResolver = new CustomContractResolver(),
Tested and worked for me

Categories