问题描述
假设我有这样的课程:
公共类示例{公共 int TypedProperty { 获取;放;}公共对象 UntypedProperty { 获取;放;}}
假设有人过来并写道:
var 示例 = 新示例{类型属性 = 5,UntypedProperty = Guid.NewGuid()}
如果我用 JsonConvert.SerializeObject(example)
序列化它,我会得到
<代码>{类型属性":5,无类型属性":24bd733f-2ade-4374-9db6-3c9f3d97b12c"}
理想情况下,我想得到这样的东西:
<代码>{类型属性":5,无类型属性":{"$type": "System.Guid,mscorlib",$值":24bd733f-2ade-4374-9db6-3c9f3d97b12c"}}
但 TypeNameHandling
在这种情况下不起作用.如何(反)序列化无类型属性?
如果你用 TypeNameHandling.All
或 TypeNameHandling.Auto
,那么当 UntypedProperty
属性将被序列化为 JSON 容器(对象或数组)时,Json.NET 应该通过将 JSON 文件中的类型信息存储在 "$ 中来正确序列化和反序列化它类型"
属性.但是,在 UntypedProperty
被序列化为 JSON 原语(字符串、数字或布尔值)的情况下,这不起作用,因为正如您所指出的,JSON 原语没有机会包含 $type"
属性.
解决方案是,在序列化具有 object
类型属性的类型时,按照 这个答案.这是一个注入此类包装器的 自定义 JSON 转换器:
公共类 UntypedToTypedValueConverter : JsonConverter{公共覆盖 bool CanConvert(Type objectType){throw new NotImplementedException("这个转换器只能通过ItemConverterType直接应用,不能添加到JsonSerializer.Converters");}public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){if (reader.TokenType == JsonToken.Null)返回空值;var value = serializer.Deserialize(reader, objectType);如果(值是 TypeWrapper){return ((TypeWrapper)value).ObjectValue;}返回值;}public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){if (serializer.TypeNameHandling == TypeNameHandling.None){Console.WriteLine("序列化程序时使用的 ObjectItemConverter.TypeNameHandling == TypeNameHandling.None");序列化器.序列化(作家,价值);}//处理几个不需要类型包装器的简单原始情况否则如果(值是字符串){writer.WriteValue((string)value);}否则如果(值为布尔值){writer.WriteValue((bool)value);}别的{var contract = serializer.ContractResolver.ResolveContract(value.GetType());如果(合同是 JsonPrimitiveContract){var wrapper = TypeWrapper.CreateWrapper(value);serializer.Serialize(writer, wrapper, typeof(object));}别的{序列化器.序列化(作家,价值);}}}}抽象类 TypeWrapper{受保护的 TypeWrapper() { }[Json忽略]公共抽象对象 ObjectValue { 获取;}public static TypeWrapper CreateWrapper(T值){如果(值 == 空)返回新的 TypeWrapper<T>();var type = value.GetType();if (type == typeof(T))返回新的 TypeWrapper(值);//返回子类的实际类型return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);}}密封类 TypeWrapper<T>: 类型包装器{公共 TypeWrapper() : base() { }公共类型包装器(T 值): 根据(){this.Value = 价值;}公共覆盖对象 ObjectValue { 获取 { 返回值;} }公共 T 值 { 获取;放;}}
然后使用 [JsonConverter(typeof(UntypedToTypedValueConverter))]
:
公共类示例{公共 int TypedProperty { 获取;放;}[JsonConverter(typeof(UntypedToTypedValueConverter))]公共对象 UntypedProperty { 获取;放;}}
如果您无法以任何方式修改 Example
类以添加此属性(您的评论 The class is not mine to change 建议您可以注入带有自定义合约解析器的转换器:
公共类 UntypedToTypedPropertyContractResolver : DefaultContractResolver{只读 UntypedToTypedValueConverter 转换器 = new UntypedToTypedValueConverter();//从 7.0.1 开始,出于性能原因,Json.NET 建议对无状态"合约解析器使用静态实例.//http://www.newtonsoft.com/json/help/html/ContractResolver.htm//http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm//在应用程序中使用合约解析器的无参数构造函数和缓存实例以获得最佳性能."//另见 https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information静态 UntypedToTypedPropertyContractResolver 实例;//显式静态构造函数告诉 C# 编译器不要将类型标记为 beforefieldinit静态 UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver();}公共静态 UntypedToTypedPropertyContractResolver 实例 { 获取 { 返回实例;} }受保护的覆盖 JsonObjectContract CreateObjectContract(Type objectType){var contract = base.CreateObjectContract(objectType);foreach(contract.Properties.Concat(contract.CreatorParameters)中的var属性){if (property.PropertyType == typeof(object)&&property.Converter == null){property.Converter = property.MemberConverter = 转换器;}}退货合同;}}
并按如下方式使用:
var settings = new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.Auto,ContractResolver = UntypedToTypedPropertyContractResolver.Instance,};var json = JsonConvert.SerializeObject(例如,Formatting.Indented,设置);var example2 = JsonConvert.DeserializeObject<示例>(json,设置);
在这两种情况下,创建的 JSON 如下所示:
<块引用><代码>{类型属性":5,无类型属性":{"$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",值":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(反)序列化无类型属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!