JSON.net(反序列化)未类型化的属性 [英] JSON.net (de)serialize untyped property

查看:92
本文介绍了JSON.net(反序列化)未类型化的属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个这样的课程:

public class Example {
    public int TypedProperty { get; set; }
    public object UntypedProperty { get; set; }
}

并假设有人出现并写道:

var example = new Example
{
    TypedProperty = 5,
    UntypedProperty = Guid.NewGuid()
}

如果我用JsonConvert.SerializeObject(example)序列化它,我会得到

{
  "TypedProperty": 5,
  "UntypedProperty": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}

理想情况下,我想得到这样的东西:

{
  "TypedProperty": 5,
  "UntypedProperty":
    {
      "$type": "System.Guid,mscorlib",
      "$value": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
    }
 }

但是TypeNameHandling在这种情况下不起作用.如何(反)序列化未类型化的属性?

解决方案

如果使用 TypeNameHandling.All TypeNameHandling.Auto, 那么当UntypedProperty属性将被序列化为JSON容器(对象或数组)时,Json.NET应该通过将类型信息存储在JSON文件中的"$type"属性中来正确地对其进行序列化和反序列化.但是,在UntypedProperty被序列化为JSON原语(字符串,数字或布尔值)的情况下,此操作不起作用,因为您已经注意到,JSON原语没有机会包含"$type"属性. /p>

解决方案是,当序列化具有类型object的属性的类型时,按照自定义JSON转换器,它会注入这样的包装器:

public class UntypedToTypedValueConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var value = serializer.Deserialize(reader, objectType);
        if (value is TypeWrapper)
        {
            return ((TypeWrapper)value).ObjectValue;
        }
        return value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (serializer.TypeNameHandling == TypeNameHandling.None)
        {
            Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
            serializer.Serialize(writer, value);
        }
        // Handle a couple of simple primitive cases where a type wrapper is not needed
        else if (value is string)
        {
            writer.WriteValue((string)value);
        }
        else if (value is bool)
        {
            writer.WriteValue((bool)value);
        }
        else
        {
            var contract = serializer.ContractResolver.ResolveContract(value.GetType());
            if (contract is JsonPrimitiveContract)
            {
                var wrapper = TypeWrapper.CreateWrapper(value);
                serializer.Serialize(writer, wrapper, typeof(object));
            }
            else
            {
                serializer.Serialize(writer, value);
            }
        }
    }
}

abstract class TypeWrapper
{
    protected TypeWrapper() { }

    [JsonIgnore]
    public abstract object ObjectValue { get; }

    public static TypeWrapper CreateWrapper<T>(T value)
    {
        if (value == null)
            return new TypeWrapper<T>();
        var type = value.GetType();
        if (type == typeof(T))
            return new TypeWrapper<T>(value);
        // Return actual type of subclass
        return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
    }
}

sealed class TypeWrapper<T> : TypeWrapper
{
    public TypeWrapper() : base() { }

    public TypeWrapper(T value)
        : base()
    {
        this.Value = value;
    }

    public override object ObjectValue { get { return Value; } }

    public T Value { get; set; }
}

然后使用 [JsonConverter(typeof(UntypedToTypedValueConverter))] 将其应用于您的类型:

public class Example
{
    public int TypedProperty { get; set; }
    [JsonConverter(typeof(UntypedToTypedValueConverter))]
    public object UntypedProperty { get; set; }
}

如果您不能以任何方式修改Example类以添加此属性(您的评论该类不是我想更改的建议尽可能多),则可以使用自定义合同解析器:

public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
    readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();

    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    // See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
    static UntypedToTypedPropertyContractResolver instance;

    // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
    static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }

    public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
        {
            if (property.PropertyType == typeof(object)
                && property.Converter == null)
            {
                property.Converter = property.MemberConverter = converter;
            }
        }
        return contract;
    }
}

并按如下所示使用它:

var settings = new JsonSerializerSettings 
{
    TypeNameHandling = TypeNameHandling.Auto,
    ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};

var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);

var example2 = JsonConvert.DeserializeObject<Example>(json, settings);

在两种情况下,创建的JSON都类似于:

{
  "TypedProperty": 5,
  "UntypedProperty": {
    "$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
    "Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
  }
}

Suppose I have a class like this:

public class Example {
    public int TypedProperty { get; set; }
    public object UntypedProperty { get; set; }
}

And suppose someone comes along and writes:

var example = new Example
{
    TypedProperty = 5,
    UntypedProperty = Guid.NewGuid()
}

If I serialize this with JsonConvert.SerializeObject(example), I get

{
  "TypedProperty": 5,
  "UntypedProperty": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}

Ideally, I'd like to get something like this:

{
  "TypedProperty": 5,
  "UntypedProperty":
    {
      "$type": "System.Guid,mscorlib",
      "$value": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
    }
 }

But TypeNameHandling doesn't work in this scenario. How can I (de)serialize an untyped property?

解决方案

If you serialize your class with TypeNameHandling.All or TypeNameHandling.Auto, then when the UntypedProperty property would be serialized as a JSON container (either an object or array) Json.NET should correctly serialize and deserialize it by storing type information in the JSON file in a "$type" property. However, in cases where UntypedProperty is serialized as a JSON primitive (a string, number, or Boolean) this doesn't work because, as you have noted, a JSON primitive has no opportunity to include a "$type" property.

The solution is, when serializing a type with a property of type object, to serialize wrappers classes for primitive values that can encapsulate the type information, along the lines of this answer. Here is a custom JSON converter that injects such a wrapper:

public class UntypedToTypedValueConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var value = serializer.Deserialize(reader, objectType);
        if (value is TypeWrapper)
        {
            return ((TypeWrapper)value).ObjectValue;
        }
        return value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (serializer.TypeNameHandling == TypeNameHandling.None)
        {
            Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
            serializer.Serialize(writer, value);
        }
        // Handle a couple of simple primitive cases where a type wrapper is not needed
        else if (value is string)
        {
            writer.WriteValue((string)value);
        }
        else if (value is bool)
        {
            writer.WriteValue((bool)value);
        }
        else
        {
            var contract = serializer.ContractResolver.ResolveContract(value.GetType());
            if (contract is JsonPrimitiveContract)
            {
                var wrapper = TypeWrapper.CreateWrapper(value);
                serializer.Serialize(writer, wrapper, typeof(object));
            }
            else
            {
                serializer.Serialize(writer, value);
            }
        }
    }
}

abstract class TypeWrapper
{
    protected TypeWrapper() { }

    [JsonIgnore]
    public abstract object ObjectValue { get; }

    public static TypeWrapper CreateWrapper<T>(T value)
    {
        if (value == null)
            return new TypeWrapper<T>();
        var type = value.GetType();
        if (type == typeof(T))
            return new TypeWrapper<T>(value);
        // Return actual type of subclass
        return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
    }
}

sealed class TypeWrapper<T> : TypeWrapper
{
    public TypeWrapper() : base() { }

    public TypeWrapper(T value)
        : base()
    {
        this.Value = value;
    }

    public override object ObjectValue { get { return Value; } }

    public T Value { get; set; }
}

Then apply it to your type using [JsonConverter(typeof(UntypedToTypedValueConverter))]:

public class Example
{
    public int TypedProperty { get; set; }
    [JsonConverter(typeof(UntypedToTypedValueConverter))]
    public object UntypedProperty { get; set; }
}

If you cannot modify the Example class in any way to add this attribute (your comment The class isn't mine to change suggests as much) you could inject the converter with a custom contract resolver:

public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
    readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();

    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    // See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
    static UntypedToTypedPropertyContractResolver instance;

    // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
    static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }

    public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
        {
            if (property.PropertyType == typeof(object)
                && property.Converter == null)
            {
                property.Converter = property.MemberConverter = converter;
            }
        }
        return contract;
    }
}

And use it as follows:

var settings = new JsonSerializerSettings 
{
    TypeNameHandling = TypeNameHandling.Auto,
    ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};

var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);

var example2 = JsonConvert.DeserializeObject<Example>(json, settings);

In both cases the JSON created looks like:

{
  "TypedProperty": 5,
  "UntypedProperty": {
    "$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
    "Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
  }
}

这篇关于JSON.net(反序列化)未类型化的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆