将JSON反序列化为通用接口属性

时间:2016-07-07 04:18:35

标签: c# .net json generics serialization

我有一个泛型类,它包含一个公共属性,它是与父类相同类型的泛型接口。下面的示例代码。

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; }
}

我正在使用JSON.net来序列化MainClass对象,该对象可以包含许多Parent<T>个对象。 Parent<T>可以是没有类型约束的任何泛型。但是,我似乎无法以通用的方式反序化生成的JSON。

我试图为JSON.net反序列化器创建一个JsonConverter,但我找不到一种通用的方法。示例JsonConverter如下。

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);
    }
}

上述转换器允许将属性放在ExampleItem类的Parent<T>属性上,如:

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

但是c#不允许在属性中使用泛型类型引用(因为属性和反射的性质)。到目前为止,我提出的唯一解决方案是在调用InterfaceJsonConverter方法之前,手动将每个预期类型T的新Deserialize()添加到序列化程序。但是,这限制了Parent<T>的可能类型,因为如果可以反序列化,则需要手动添加每种类型。

有没有办法以通用方式反序列化?我应该采取另一种方法吗?

1 个答案:

答案 0 :(得分:3)

这可以通过将open generic type typeof(Example<>)作为构造函数参数传递给适当的JsonConverter,然后在ReadJson()内构建适当的封闭泛型类型来间接完成假设传入的objectType具有与所需的具体封闭泛型类型相同的通用参数。

另请注意,只要转换器使用[JsonConverter(Type,Object[])]直接应用于属性,转换器就不必知道接口类型,因为不会调用CanConvert()。仅当转换器位于JsonSerializer.Converters列表中时才会调用CanConvert()

因此您的转换器变为:

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();
    }
}

按如下方式应用:

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

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

要将带参数的转换器应用于集合项,请使用JsonPropertyAttribute.ItemConverterTypeJsonPropertyAttribute.ItemConverterParameters,例如:

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; }
}