Skip to content

System.Text.Json.Serialization: Converters are ignored for types derived from supported base types #30619

Description

@phizch

cc: @layomia,@ahsonkhan, @scalablecory, @steveharter

Related:

Description

Pull dotnet/corefx#39001 implemented by @layomia added support for serialization of types that implements natively supported collections without needing to create converters for them.

Unfortunately the implementation treats all derived types as the base type, so it's not possible to create specialized collections with converters. E.g. class MyCollection : List<int> gets treated as List<int>.

Specialized collections is necessary if the json has multiple ways of declaring a list, e.g.

{
  "SpecialList" : 
  {
    "Count": 2,
    "Values" : [ 1, 2 ]
  },
  "NormalList" : [ 1, 2 ] 
}
public class A
{
    [JsonConverter(typeof(MyCollectionConverter))] // this is ignored and converter for List<int> is used instead
    public MyCollection SpecialList { get; set; }
    public List<int> NormalList { get; set }
}

[JsonConverter(typeof(MyCollectionConverter))] // this is ignored and converter for List<int> is used instead
public class MyCollection : List<int> { }

public class MyCollectionConverter : JsonConverter<MyCollection>
{
    ....
}
public class MyCollectionFactory : JsonConverterFactory
{
	public override bool CanConvert(Type typeToConvert)
    {
        // this only sees List<int>
    }
}
public static void RunTest()
{
    JsonSerializerOptions options = new JsonSerializerOptions
    {
        Converters = 
        { 
            new MyCollectionFactory(), 
            new  MyCollectionConverter() // this is ignored and converter for List<int> is used 
        }
    };

    _ = JsonSerializer.Deserialize<A>("{}", options);
}

Expected priorities for getting the converter:

// Priority 1: attempt to get converter from JsonConverterAttribute on property.
// Priority 2: Attempt to get custom converter added at runtime.
// Priority 3: Attempt to get converter from [JsonConverter] on the type being converted.
// Priority 4: Attempt to get built-in converter.

Current behavior

// Priority 0: if type is derived from supported type, use converter for base type
// Priority 1: attempt to get converter from JsonConverterAttribute on property.
// Priority 2: Attempt to get custom converter added at runtime.
// Priority 3: Attempt to get converter from [JsonConverter] on the type being converted.
// Priority 4: Attempt to get built-in converter.

From JsonClassInfo.AddProperty [source]From JsonClassInfo.AddProperty source:

    // Get implemented type, if applicable.
    // Will return the propertyType itself if it's a non-enumerable, string, or natively supported collection.
    Type implementedType = GetImplementedCollectionType(propertyType);
    if (implementedType != propertyType)
    {
        jsonInfo = CreateProperty(implementedType, implementedType, implementedType, null, typeof(object), options);
    }
    else
    {
        jsonInfo = CreateProperty(propertyType, propertyType, propertyType, propertyInfo, classType, options);
    }

In my example GetImplementedCollectionType(typeof(MyCollection)) will return List<int>.

I'm currently looking into it to see if I can find a fix.

--
Just found another bug: Since propertyInfo is ignored any JsonIgnoreAttribute will also be ignored.
e.g.

public class C
{
    public int Version { get; set; }
    
    [JsonIgnore] // this will be ignored
    public MyCollection { get; set; }
}

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions