neuecc / Utf8Json

Definitely Fastest and Zero Allocation JSON Serializer for C#(NET, .NET Core, Unity, Xamarin).
MIT License
2.35k stars 266 forks source link

Deserialize class with property which type is declared as interface #163

Open andrew-ew opened 4 years ago

andrew-ew commented 4 years ago

Hi, Let's say we have the following declaration of interfaces an classes:

public interface IDataHolder
{
    byte[] Data { get; set; }
}

public class DataHolder : IDataHolder
{
    public byte[] Data { get; set; }
}

public class Data
{
    public IDataHolder DataHolder { get; set; }
}

Then I am trying to do the following:

var data = new Data {DataHolder = new DataHolder { Data = new byte[] {1, 2, 3}} };
var json = Utf8Json.JsonSerializer.ToJsonString(data);
data = Utf8Json.JsonSerializer.Deserialize<Data>(json);

The last line throws an error: System.InvalidOperationException: 'generated serializer for IDataHolder does not support deserialize.'

This definitely must be something easy but from documentation I cannot understand how to solve this. Please help.

jp-kaspar commented 3 years ago

Not sure if this is still pending; but I'm having similar issues. As for your specific problem; my guess is that the generated serializer probably is unable to deserialize to an interface (IDataHolder), since it cannot create such an instance. You could try using a JsonConverter using a discriminator field to provide the serializer with concrete type information. This is supported by Newtonsoft (and also System.Text.Json I'd say).

petrkoutnycz commented 3 years ago

It'd be really cool to have workaround at least.

jp-kaspar commented 3 years ago

For what it is worth: I solved my issue by creating a custom IJsonFormatter. The type, in my case, was JSonDocument.

/// <summary>
/// Custom Utf8Json formatter for JsonDocument instances. The Utf8Json standard generated formatter
/// is unable to deserialize a JsonDocument instance. (serializing also always results in null) 
/// </summary>
public sealed class Utf8Json_JsonDocFormatter : IJsonFormatter<JsonDocument>
{

    public void Serialize(ref JsonWriter writer, JsonDocument value, IJsonFormatterResolver formatterResolver)
    {
        if (value == null)
            writer.WriteString(string.Empty);
        else
        {
            // Write out the actual bytes. This might be a slow process so be prudent with 
            // JsonDocument properties!
            using (var stream = new System.IO.MemoryStream())
            {
                Utf8JsonWriter utf8Writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
                value.WriteTo(utf8Writer);
                utf8Writer.Flush();
                writer.WriteString(System.Text.Encoding.UTF8.GetString(stream.ToArray()));
            }
        }
    }

    public JsonDocument Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        var token = reader.GetCurrentJsonToken();

        if (token == JsonToken.String)
        {
            var content = reader.ReadString();
            // parse the JSON. Let exceptions fly and nuke the caller - we don't want to TryParse or catch anything here
            // for the sake of performance.
            return string.IsNullOrEmpty(content) ? null : JsonDocument.Parse(content);
        }
        return null;
    }
}

And then added it:

            Utf8Json.Resolvers.CompositeResolver.RegisterAndSetAsDefault(
            new Utf8Json.IJsonFormatter[] 
            {
                 new Utf8Json_JsonDocFormatter() 
            }, 
            new Utf8Json.IJsonFormatterResolver[] 
            {
                Utf8Json.Resolvers.BuiltinResolver.Instance,
                Utf8Json.Resolvers.StandardResolver.Default
            });

EDIT: I am not sure if this also works on interfaces; but there are no type constraints on IJsonFormatter, so in theory it should.