Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 874
jeff17237 Avatar asked Jul 07 '16 04:07

jeff17237


People also ask

What is Deserializing a JSON?

The process whereby a lower-level format (e.g. that has been transferred over a network, or stored in a data store) is translated into a readable object or other data structure. In JavaScript, for example, you can deserialize a JSON string to an object by calling the function JSON. parse() .

Is JSON serialized or Deserialized?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object). If you serialize this result it will generate a text with the structure and the record returned.

Is polymorphic deserialization possible in system text JSON?

Text. Json doesn't support the serialization of polymorphic type hierarchies. For example, if a property's type is an interface or an abstract class, only the properties defined on the interface or abstract class are serialized, even if the runtime type has additional properties.

What is System Text JSON?

Json Namespace. Provides standards-based support for the serialization of JavaScript Object Notation (JSON).


1 Answers

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, e.g.:

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; }
}
like image 177
dbc Avatar answered Sep 25 '22 08:09

dbc