Search code examples
c#serializationpolymorphismsystem.text.json

Can I customize and shorten the polymorphic type discriminator property name and value when serializing?


Is it possible to shorten the type discriminator property name and value during serialization/deserialization? I don't have access to the types itself so in my case it should be done by configuring serializer itself. I need the property name to be "__type" and the property value to be some custom name.

I know that in System.Web.Script.Serialization JavaScriptTypeResolver could be used but I need to implement in .NET Core System.Text.Json.

Here is an example how it can be done using JavaScriptTypeResolver (the example is based on this MS example):

var people = new Person[] { new Person { Name = "AbcPerson" } };

var serialized = new JavaScriptSerializer(new CustomTypeResolver()).Serialize(people);
Console.WriteLine(serialized);

...

public class Person
{
    public string Name { get; set; }
}

public class CustomTypeResolver : JavaScriptTypeResolver
{
    private readonly IDictionary<string, Type> _typeIds
        = new Dictionary<string, Type> { { "P", typeof(Person) } };

    public override Type ResolveType(string id)
    {
        _typeIds.TryGetValue(id, out var result);
        return result;
    }

    public override string ResolveTypeId(Type type)
    {
        return _typeIds.SingleOrDefault(x => x.Value == type).Key;
    }
}

The output is:

[{"__type":"P","Name":"AbcPerson"}]

EDIT: Partial solution is to use attributes like this [JsonDerivedType(typeof(Person), typeDiscriminator: "P")]. But then output is like this

[{"$type":"P","Name":"AbcPerson"}]

And I'm left with a problem of changing $type to __type.


Solution

  • To change the type discriminator property name to "__type" (which is the discriminator name that was used by JavaScriptSerializer and DataContractJsonSerializer), you need to set [JsonPolymorphic(TypeDiscriminatorPropertyName = "__type")] on every type for which you want to emit a type discriminator. For instance, if your data model has a base type Person and a derived type Adult, you would need to modify them as follows:

    [JsonPolymorphic(TypeDiscriminatorPropertyName = "__type")] // Add this here
    [JsonDerivedType(typeof(Person), typeDiscriminator: "P")]
    [JsonDerivedType(typeof(Adult), typeDiscriminator: "A")]
    public class Person
    {
        public string Name { get; set; }
    }
    
    [JsonPolymorphic(TypeDiscriminatorPropertyName = "__type")] // And also here
    [JsonDerivedType(typeof(Adult), typeDiscriminator: "A")]
    public class Adult : Person;
    

    Demo fiddle #1 here.

    Alternatively, if you cannot modify your data models easily, you can change the discriminator name via a JsonTypeInfo modifier:

    public static partial class JsonExtensions
    {
        public const string MyTypeDiscriminatorPropertyName = "__type";
        
        public static Action<JsonTypeInfo> SetupMyTypeDiscriminatorPropertyName() => 
            SetupTypeDiscriminatorPropertyName(MyTypeDiscriminatorPropertyName);
    
        public static Action<JsonTypeInfo> SetupTypeDiscriminatorPropertyName(string name) => 
            (JsonTypeInfo typeInfo) =>
        {
            if (typeInfo.PolymorphismOptions != null)
                typeInfo.PolymorphismOptions.TypeDiscriminatorPropertyName = name;
        };
    }
    

    Then use it in options e.g. as follows:

    var options = new JsonSerializerOptions
    {
        TypeInfoResolver = new DefaultJsonTypeInfoResolver() 
            .WithAddedModifier(JsonExtensions.SetupMyTypeDiscriminatorPropertyName()),
        // Add other options as required.
    };
    
    var json = JsonSerializer.Serialize(people, options);
    
    var people2 = JsonSerializer.Deserialize<Person []>(json, options);
    

    Demo fiddle #2 here.

    See also: