Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add json deserialize interface attribute to specific class #63436

Closed
erikthysell opened this issue Jan 6, 2022 · 6 comments
Closed

Add json deserialize interface attribute to specific class #63436

erikthysell opened this issue Jan 6, 2022 · 6 comments
Labels
area-System.Text.Json question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@erikthysell
Copy link

For me, having a lot of classes with interfaces, it would be nice with a simple property attribute for deserialization to a specific concrete class, instead of having to write custom converters each time.
I.e. I propose something along this :

[JsonHandleAs(typeof(MyClass))]
public  IMyClass MyProperty{ get; set; }

Perhaps this already is implemented, however I can't seem to find the documentation for it, even after quite some searching.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Jan 6, 2022
@ghost
Copy link

ghost commented Jan 6, 2022

Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.

Issue Details

For me, having a lot of classes with interfaces, it would be nice with a simple property attribute for deserialization to a specific concrete class, instead of having to write custom converters each time.
I.e. I propose something along this :

[JsonHandleAs(typeof(MyClass))]
public  IMyClass MyProperty{ get; set; }

Perhaps this already is implemented, however I can't seem to find the documentation for it, even after quite some searching.

Author: erikthysell
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

@layomia
Copy link
Contributor

layomia commented Jan 13, 2022

@eiriktsarpalis this seems like a design consideration for the polymorphic serialization feature - #30083.

@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Jan 13, 2022
@layomia layomia added this to the 7.0.0 milestone Jan 13, 2022
@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Jan 13, 2022

Note that since serialization is not covariant this only really works well for deserialization. For serialization there is a good chance of you getting a runtime cast error, if the value of the serialized property is not of type MyClass. For that reason I don't think we should be adding such functionality out of the box in System.Text.Json.

Nevertheless, it should still be possible to define your own JsonHandleAsAttribute using the existing extensibility capabilities of the library:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

string json = JsonSerializer.Serialize<Foo>(new Bar { A = 1, B = 2 });
Bar bar = (Bar)JsonSerializer.Deserialize<Foo>(json)!;
Console.WriteLine(bar.B);

[JsonHandleAs(typeof(Bar))]
public class Foo
{
    public int A { get; set; }
}

public class Bar : Foo
{
    public int B { get; set; }
}


public class JsonHandleAsAttribute : JsonConverterAttribute
{
    private readonly Type _derivedType;

    public JsonHandleAsAttribute(Type derivedType) => _derivedType = derivedType;

    public override JsonConverter? CreateConverter(Type _) => new DerivedTypeConverter(_derivedType);
}

public class DerivedTypeConverter : JsonConverterFactory
{
    private readonly Type _derivedType;

    public DerivedTypeConverter(Type derivedType) => _derivedType = derivedType;

    public override bool CanConvert(Type typeToConvert)
        // NB check doesn't cover interface implementations which require more involved reflection.
        // Interface support left as exercise to the reader.
        => typeToConvert != _derivedType && typeToConvert.IsAssignableFrom(_derivedType);

    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        var converterType = typeof(DerivedTypeConverter<,>).MakeGenericType(_derivedType, typeToConvert);
        return (JsonConverter)Activator.CreateInstance(converterType, options)!;
    }
}

public class DerivedTypeConverter<TDerived, TBase> : JsonConverter<TBase>
    where TDerived : TBase
{
    private readonly JsonConverter<TDerived> _derivedConverter;

    public DerivedTypeConverter(JsonSerializerOptions jsonSerializerOptions)
    {
        _derivedConverter = (JsonConverter<TDerived>)jsonSerializerOptions.GetConverter(typeof(TDerived));
    }

    public override TBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // Type-safe upcast to TBase
        return _derivedConverter.Read(ref reader, typeof(TDerived), options);
    }

    public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
    {
        // Downcast can fail at runtime!
        _derivedConverter.Write(writer, (TDerived)value!, options);
    }
}

@eiriktsarpalis eiriktsarpalis added the question Answer questions and provide assistance, not an issue with source code or documentation. label Jan 13, 2022
@erikthysell
Copy link
Author

Thanks a lot @eiriktsarpalis !

@erikthysell
Copy link
Author

erikthysell commented Feb 8, 2022

@eiriktsarpalis : I am fairly new to this.. but would it be possible to tweak your code above to work with serialization of the following snippet:

var p = new ParentClass();
for (var i = 0; i < 10; i++)
{
    p.MyChildren.Add(new Child());
}
var familyTree = JsonSerializer.Serialize(p);

interface IChild
{
}

class Child : IChild
{
}

class ParentClass
{
    [JsonHandleAs(typeof(List<Child>))]
    public List<IChild> MyChildren { get; set; } = new();
}

When I try this I get the following exception:

System.InvalidOperationException: 'The converter specified on 'ParentClass.MyChildren' is not compatible with the type 'System.Collections.Generic.List`1[IChild]'.'

@eiriktsarpalis
Copy link
Member

@erikthysell that's not supported, but the request is tracked by a separate issue #54189.

@ghost ghost locked as resolved and limited conversation to collaborators Mar 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

3 participants