Json.Net Serialization of Type With Polymorphic Child Object

Json.Net Serialization of Type with Polymorphic Child Object

Having the subtype information in the container class is problematic for two reasons:

  1. The container class instance is not accessible when Json.NET is reading the contained class.
  2. If you later need to convert the 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();
}
}

System.Text.Json - Polymorphic serialization for property is not working

A simpler way of doing it that works with any type is to cast the object to System.Object. This makes use of the fact that the runtime type is used when serializing System.Object.

JsonSerializer.Serialize((object)request, options);

However, this will only apply the polymorphic serialization to the root object. If the polymorphic object is not the root and is instead stored as a property of the root, then using a custom converter is useful. In order to make that converter more generic, you can give it a generic type.

class PolymoprphicConverter<T> : JsonConverter<T> {}

The rest stays the same but replacing KrakenSubscriptionDetails with T. When you want to serialize, simply specify the type as the generic

class KrakenSubscriptionDetailsParent
{
public KrakenSubscriptionDetails Details { get; set; }
}

KrakenSubscriptionDetailsParent parent = ...
options.Converters.Add(new PolymoprphicConverter<KrakenSubscriptionDetails>());
JsonSerializer.Serialize(parent, options);

A downside of this approach is that it does not work well with tree structures where the parent type is or derived from the child type. If you pass the options to JsonSerializer.Serialize, it will try to use the same converter to serialize the object, causing an infinite loop. To prevent this, you must make sure to remove the converter from the options before serializing, preventing the converter from being applied to children.



Related Topics



Leave a reply



Submit