The solution:
You will need custom Json converter so that you can resolve ISubclass1
with the concrete types you desire.
1 - Add this generic converter to your solution.
public abstract class JsonCreationConverter<T> : JsonConverter
{
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">
/// contents of JSON object that will be deserialized
/// </param>
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
}
2 - Then inherit any interface you want to translate like the example below. Take note that FieldExists
check is checking for a field that would identify the type of subclass. This way Json do not need $type
information in order to deserialize correctly.
Reference: BulletProof Deserialization
public class SubClass1Converter : JsonCreationConverter<ISubClass1>
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new InvalidOperationException("Use default serialization.");
}
protected override ISubClass1 Create(Type objectType, JObject jObject)
{
if (FieldExists("string2", jObject))
{
return new SubClass2();
}
else
{
return new SubClass1();
}
}
private bool FieldExists(string fieldName, JObject jObject)
{
return jObject[fieldName] != null;
}
}
3 - You have modify your subclasses and implementations with Json.net Attributes, else deserialization will default values to null. You have an immutable class with some interface implemented.
Some keys points:
[JsonProperty]
needs to be present on every property, else the properties will be read as null;
[public SubClass1() { }]
an empty constructor needs to be present for converters declare an empty class.
- `[JsonConstructor] ' needs to be marked on the constructor that allow properties to get values passed to them in the immutable objects.
{ get; }
needs to be changed to { get; private set; }
else all properties will set to null
public ISubClass1 SomeInterface { get; private set;}
in the implementation needs to be marked with [JsonConverter(typeof(SubClass1Converter))]
attribute.
Subclass:
public class SubClass1 : ISubClass1
{
[JsonProperty]
public int number { get; private set;}
[JsonProperty]
public string string1 { get; private set;}
public SubClass1() { }
[JsonConstructor]
public SubClass1(int a, string str1)
{
number = a;
string1 = str1;
}
}
public class SubClass2 : ISubClass1
{
[JsonProperty]
public int number { get; private set;}
[JsonProperty]
public string string1 { get; private set;}
[JsonProperty]
public string string2 { get; private set;}
public SubClass2() { }
[JsonConstructor]
public SubClass2(int a, string str1, string str2)
{
number = a;
string1 = str1;
string2 = str2;
}
}
Implementation:
class Class1
{
[JsonProperty]
public int SomeInt{ get; private set;}
[JsonProperty]
[JsonConverter(typeof(SubClass1Converter))]
public ISubClass1 SomeInterface { get; private set;}
[JsonConstructor]
public Class1(int a, ISubClass1 subclass)
{
SomeInt= a;
SomeInterface = subclass;
}
}
Usage:
JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.Formatting = Formatting.Indented;
jsonSettings.Converters.Add(new SubClass1Converter()); //Optional
jsonSettings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; //Optional
string jsonStr = JsonConvert.SerializeObject(cls1, jsonSettings);
Class1 deserializedObject = JsonConvert.DeserializeObject<Class1>(jsonStr , jsonSettings);
References: