简体   繁体   中英

Deserializing JSON to a generic interface property

I have a generic class which contains a public property which is a generic interface of the same type as the parent class. Example code below.

public interface IExample<T>
{
    T Value { get; set; }
    string Name { get; set; }
}

public class Example<T> : IExample<T>
{
    public string Name { get; set; }
    public T Value { get; set; }
}

public class Parent<T>
{
    public string ParentName { get; set; }
    public IExample<T> ExampleItem { get; set; }
}

public class MainClass
{
    public Parent<int> IntParent { get; set; }
}

I am using JSON.net to serialize the MainClass object which can contain many Parent<T> objects. Parent<T> can be any generic with no type constraints. However, I cannot seem to deserialize the resulting JSON in a generic fashion.

I attempted to create a JsonConverter for the JSON.net deserializer, but I cannot find a way to apply it generically. Example JsonConverter below.

public class InterfaceJsonConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface, new()
{
    public override bool CanConvert(Type objectType)
    {
        return (typeof(TInterface) == objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize<TImplementation>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

The above converter would allow an attribute to be placed on the ExampleItem property of the Parent<T> class like:

public class Parent<T>
{
    public string ParentName { get; set; }
    [JsonConverter(typeof(InterfaceJsonConverter<IExample<T>, Example<T>>))]
    public IExample<T> ExampleItem { get; set; }
}

But c# does not let you have generic type references in attributes (because of the nature of attributes and reflection). The only solution I have come up with so far is to add a new InterfaceJsonConverter for each expected type T manually to the serializer before calling the Deserialize() method. However, this limits the possible types of Parent<T> since each type needs to be added manually if it can be deserialized.

Is there any way to deserialize this in a generic way? Is there another approach I should be taking?

This can be done, albeit indirectly, by passing the open generic type typeof(Example<>) to an appropriate JsonConverter as a constructor argument, then inside ReadJson() constructing an appropriate closed generic type by assuming the objectType passed in has the same generic parameters as the desired concrete closed generic type.

Note also that, as long as the converter is applied directly to a property using [JsonConverter(Type,Object[])] , it is not necessary for the converter to know the interface type, since CanConvert() will not be called. CanConvert() is only called when the converter is in the JsonSerializer.Converters list.

Thus your converter becomes:

public class InterfaceToConcreteGenericJsonConverter : JsonConverter
{
    readonly Type GenericTypeDefinition;

    public InterfaceToConcreteGenericJsonConverter(Type genericTypeDefinition)
    {
        if (genericTypeDefinition == null)
            throw new ArgumentNullException();
        this.GenericTypeDefinition = genericTypeDefinition;
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    Type MakeGenericType(Type objectType)
    {
        if (!GenericTypeDefinition.IsGenericTypeDefinition)
            return GenericTypeDefinition;
        try
        {
            var parameters = objectType.GetGenericArguments();
            return GenericTypeDefinition.MakeGenericType(parameters);
        }
        catch (Exception ex)
        {
            // Wrap the reflection exception in something more useful.
            throw new JsonSerializationException(string.Format("Unable to construct concrete type from generic {0} and desired type {1}", GenericTypeDefinition, objectType), ex);
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(reader, MakeGenericType(objectType));
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Apply it as follows:

public class Parent<T>
{
    public string ParentName { get; set; }

    [JsonConverter(typeof(InterfaceToConcreteGenericJsonConverter), new object[] { typeof(Example<>) })]
    public IExample<T> ExampleItem { get; set; }
}

To apply such a converter with parameters to collection items, use JsonPropertyAttribute.ItemConverterType and JsonPropertyAttribute.ItemConverterParameters , eg:

public class Parent<T>
{
    public string ParentName { get; set; }

    [JsonProperty(ItemConverterType = typeof(InterfaceToConcreteGenericJsonConverter), ItemConverterParameters = new object[] { typeof(Example<>) })]
    public List<IExample<T>> ExampleList { get; set; }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM