We would like to be able to serialize/deserialize json from/to C# classes, with the main class having an instance of a polymorphic child object. Doing so is easy using Json.Net's TypeNameHandling.Auto setting. However, we would like to do so without the "$type" field.
The first thought is to be able to rename "$type" to a value of our choosing and to have the value for the type be an enum that would map the sub-types properly. I have not seen that as being an option but would be glad to hear if it is possible.
The second thought was along the following lines... Below is a first pass at classes, with the top level class having an indicator (SubTypeType) as to what type of data is contained in the child object (SubTypeData). I've dug around a bit into the Json.Net documentation and have tried a few things but have had no luck.
We currently have full control over the data definition, but once it is deployed, then things are locked.
public class MainClass
{
public SubType SubTypeType { get; set; }
public SubTypeClassBase SubTypeData { get; set; }
}
public class SubTypeClassBase
{
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
Having the subtype information in the container class is problematic for two reasons:
SubTypeClassBase
property into, say, a list, there will be nowhere to put the subtype information.Instead, I would recommend adding the subtype information as a property in the base class:
[JsonConverter(typeof(SubTypeClassConverter))]
public class SubTypeClassBase
{
[JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value
public SubType Type { get { return typeToSubType[GetType()]; } }
}
Now the custom subtype enum will be serialized whenever an object assignable to SubTypeClassBase
is serialized. Having done that, for deserialization you can create a JsonConverter
that loads the json for a given SubTypeClassBase
into a temporary JObject
, checks the value of the "Type"
property, and deserializes the JSON object as the appropriate class.
Prototype implementation below:
public enum SubType
{
BaseType,
Type1,
Type2,
}
[JsonConverter(typeof(SubTypeClassConverter))]
public class SubTypeClassBase
{
static readonly Dictionary<Type, SubType> typeToSubType;
static readonly Dictionary<SubType, Type> subTypeToType;
static SubTypeClassBase()
{
typeToSubType = new Dictionary<Type,SubType>()
{
{ typeof(SubTypeClassBase), SubType.BaseType },
{ typeof(SubTypeClass1), SubType.Type1 },
{ typeof(SubTypeClass2), SubType.Type2 },
};
subTypeToType = typeToSubType.ToDictionary(pair => pair.Value, pair => pair.Key);
}
public static Type GetType(SubType subType)
{
return subTypeToType[subType];
}
[JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value
public SubType Type { get { return typeToSubType[GetType()]; } }
}
public class SubTypeClass1 : SubTypeClassBase
{
public string AaaField { get; set; }
}
public class SubTypeClass2 : SubTypeClassBase
{
public string ZzzField { get; set; }
}
public class SubTypeClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SubTypeClassBase);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
var typeToken = token["Type"];
if (typeToken == null)
throw new InvalidOperationException("invalid object");
var actualType = SubTypeClassBase.GetType(typeToken.ToObject<SubType>(serializer));
if (existingValue == null || existingValue.GetType() != actualType)
{
var contract = serializer.ContractResolver.ResolveContract(actualType);
existingValue = contract.DefaultCreator();
}
using (var subReader = token.CreateReader())
{
// Using "populate" avoids infinite recursion.
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With