当某些 $type 未知时使用 TypeNameHandling 反序列化一组对象的最佳方法
Best way to deserialize set of objects with TypeNameHandling when some $type are unknown
我目前正在使用 json.net 反序列化和实例化一组来自 REST 服务的对象。
此服务为我提供了许多对象类型,因此我必须使用 TypeNameHandling.Objects
和自定义活页夹来匹配我的程序集类型。
来自服务的响应示例:
{
"rows":[
{
"id":"id1",
"doc":{
"$type":"Car",
"color":"blue"
}
},
{
"id":"id36",
"doc":{
"$type":"Dog",
"name":"hodor"
}
},
{
"id":"id52",
"doc":{
"$type":"Human",
"name":"gandalf"
}
}
]
}
我的程序集中的类型是:
行数:
public class Rows
{
public List<Row> rows {get; set;}
}
行:
public class Row
{
public string id {get; set;}
public IDocument doc {get; set;}
}
IDocument:
public class Dog : IDocument
{
public string id {get; set;}
public string name {get; set;}
}
汽车:
public class Car : IDocument
{
public string id {get; set;}
public string color {get; set;}
}
狗:
public class Dog : IDocument
{
public string id {get; set;}
public string name {get; set;}
}
我想使用自定义活页夹将此答案反序列化为 Rows
对象:
public class DocumentBinder : ISerializationBinder
{
public Type BindToType(string assemblyName, string typeName)
{
if (typeName == "Dog")
return typeof(Dog);
else if (typeName == "Car")
return typeof(Car);
throw new JsonSerializationException();
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
if (serializedType == typeof(Dog))
typeName = "Dog";
else if (serializedType == typeof(Car))
typeName = "Car";
else
throw new JsonSerializationException();
}
}
我的问题是其中一些类型在我的程序集中是未知的,假设在我的示例中是 Human
,我不想建模 Human
。我可以这样做,但可以更新 REST 服务以添加新类型,而我对该服务没有任何控制权。
我发现从该服务反序列化所有对象的唯一方法是:
- 使用
JObject.Parse()
将 Rows 对象转换为 JObject
- 遍历
rows
以逐一反序列化对象,忽略 $type
未知时抛出的 JsonSerializationException
。
它可以工作,但性能很差,它需要在开始反序列化之前转储整个 HTTP 应答。
为了提高性能我想使用 StreamReader
和 JsonTextReader
但我不能,只要我找不到方法告诉 json.net 忽略未知 $type
和 return 一个空值(如果需要)。
如果所有类型都是已知的,它会按预期与流一起工作,但一旦缺少一种类型,它就会抛出 JsonSerializationException
.
在我的示例中,对于完全反序列化的 Row
对象中的 Human
类型,我希望 doc
为 null。
有没有办法将未知类型视为 null
?我需要自己的转换器吗?
你可以适应 from and throw a custom exception, inheriting from JsonSerializationException
, from your custom serialization binder. Then, handle that exception using Newtonsoft's exception handling mechanism.
按如下方式修改您的活页夹:
public class DocumentBinder : KnownTypesBinder
{
static readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>
{
{"Dog", typeof(Dog)},
{"Car", typeof(Car)},
};
public DocumentBinder() : base(nameToType) { }
}
public class KnownTypesBinder : ISerializationBinder
{
readonly Dictionary<string, Type> nameToType;
readonly Dictionary<Type, string> typeToName;
public KnownTypesBinder(IEnumerable<KeyValuePair<string, Type>> nameToType)
{
this.nameToType = nameToType.ToDictionary(p => p.Key, p => p.Value);
this.typeToName = nameToType.ToDictionary(p => p.Value, p => p.Key);
}
public Type BindToType(string assemblyName, string typeName)
{
if(nameToType.TryGetValue(typeName, out var type))
return type;
throw new JsonSerializationBinderException(string.Format("Unknown type name {0} ({1})", typeName, assemblyName));
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
if(!typeToName.TryGetValue(serializedType, out typeName))
throw new JsonSerializationBinderException(string.Format("Unknown type {0}", serializedType));
assemblyName = null;
}
}
public class JsonSerializationBinderException : JsonSerializationException
{
public JsonSerializationBinderException() { }
public JsonSerializationBinderException(string message) : base(message) { }
public JsonSerializationBinderException(string message, Exception innerException) : base(message, innerException) { }
public JsonSerializationBinderException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
然后按如下方式初始化您的 JsonSerializerSettings
:
var deserializationSettings = new JsonSerializerSettings
{
SerializationBinder = new DocumentBinder(),
TypeNameHandling = TypeNameHandling.Objects, // Or Auto as appropriate
Error = (sender, args) =>
{
if (args.CurrentObject == args.ErrorContext.OriginalObject
&& args.ErrorContext.Error.GetBaseException() is JsonSerializationBinderException
)
{
args.ErrorContext.Handled = true;
}
},
// Other settings as required
};
使用这些设置,您将能够直接从流中反序列化 JSON 并跳过未知的 IDocument
类型,而无需加载和预处理 JToken
层次结构。
备注:
Json.NET可能会将一个应用程序抛出的异常包裹在多层异常中,所以必须使用GetBaseException()
来判断是否抛出自定义异常。
您可能不想在 序列化 时处理 JsonSerializationBinderException
,因为序列化时出现异常将表明您的活页夹中存在错误或缺少类型。
通过只处理您的自定义异常,您可以确保不会吞噬其他错误引起的异常。例如。由格式错误的 JSON 文件引起的 JsonReaderException
永远不应被吞下,因为这样做可能会导致 JsonTextReader
陷入无限循环。
演示 fiddle here.
我目前正在使用 json.net 反序列化和实例化一组来自 REST 服务的对象。
此服务为我提供了许多对象类型,因此我必须使用 TypeNameHandling.Objects
和自定义活页夹来匹配我的程序集类型。
来自服务的响应示例:
{
"rows":[
{
"id":"id1",
"doc":{
"$type":"Car",
"color":"blue"
}
},
{
"id":"id36",
"doc":{
"$type":"Dog",
"name":"hodor"
}
},
{
"id":"id52",
"doc":{
"$type":"Human",
"name":"gandalf"
}
}
]
}
我的程序集中的类型是:
行数:
public class Rows
{
public List<Row> rows {get; set;}
}
行:
public class Row
{
public string id {get; set;}
public IDocument doc {get; set;}
}
IDocument:
public class Dog : IDocument
{
public string id {get; set;}
public string name {get; set;}
}
汽车:
public class Car : IDocument
{
public string id {get; set;}
public string color {get; set;}
}
狗:
public class Dog : IDocument
{
public string id {get; set;}
public string name {get; set;}
}
我想使用自定义活页夹将此答案反序列化为 Rows
对象:
public class DocumentBinder : ISerializationBinder
{
public Type BindToType(string assemblyName, string typeName)
{
if (typeName == "Dog")
return typeof(Dog);
else if (typeName == "Car")
return typeof(Car);
throw new JsonSerializationException();
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
assemblyName = null;
if (serializedType == typeof(Dog))
typeName = "Dog";
else if (serializedType == typeof(Car))
typeName = "Car";
else
throw new JsonSerializationException();
}
}
我的问题是其中一些类型在我的程序集中是未知的,假设在我的示例中是 Human
,我不想建模 Human
。我可以这样做,但可以更新 REST 服务以添加新类型,而我对该服务没有任何控制权。
我发现从该服务反序列化所有对象的唯一方法是:
- 使用
JObject.Parse()
将 Rows 对象转换为 - 遍历
rows
以逐一反序列化对象,忽略$type
未知时抛出的JsonSerializationException
。
JObject
它可以工作,但性能很差,它需要在开始反序列化之前转储整个 HTTP 应答。
为了提高性能我想使用 StreamReader
和 JsonTextReader
但我不能,只要我找不到方法告诉 json.net 忽略未知 $type
和 return 一个空值(如果需要)。
如果所有类型都是已知的,它会按预期与流一起工作,但一旦缺少一种类型,它就会抛出 JsonSerializationException
.
在我的示例中,对于完全反序列化的 Row
对象中的 Human
类型,我希望 doc
为 null。
有没有办法将未知类型视为 null
?我需要自己的转换器吗?
你可以适应JsonSerializationException
, from your custom serialization binder. Then, handle that exception using Newtonsoft's exception handling mechanism.
按如下方式修改您的活页夹:
public class DocumentBinder : KnownTypesBinder
{
static readonly Dictionary<string, Type> nameToType = new Dictionary<string, Type>
{
{"Dog", typeof(Dog)},
{"Car", typeof(Car)},
};
public DocumentBinder() : base(nameToType) { }
}
public class KnownTypesBinder : ISerializationBinder
{
readonly Dictionary<string, Type> nameToType;
readonly Dictionary<Type, string> typeToName;
public KnownTypesBinder(IEnumerable<KeyValuePair<string, Type>> nameToType)
{
this.nameToType = nameToType.ToDictionary(p => p.Key, p => p.Value);
this.typeToName = nameToType.ToDictionary(p => p.Value, p => p.Key);
}
public Type BindToType(string assemblyName, string typeName)
{
if(nameToType.TryGetValue(typeName, out var type))
return type;
throw new JsonSerializationBinderException(string.Format("Unknown type name {0} ({1})", typeName, assemblyName));
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
if(!typeToName.TryGetValue(serializedType, out typeName))
throw new JsonSerializationBinderException(string.Format("Unknown type {0}", serializedType));
assemblyName = null;
}
}
public class JsonSerializationBinderException : JsonSerializationException
{
public JsonSerializationBinderException() { }
public JsonSerializationBinderException(string message) : base(message) { }
public JsonSerializationBinderException(string message, Exception innerException) : base(message, innerException) { }
public JsonSerializationBinderException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
然后按如下方式初始化您的 JsonSerializerSettings
:
var deserializationSettings = new JsonSerializerSettings
{
SerializationBinder = new DocumentBinder(),
TypeNameHandling = TypeNameHandling.Objects, // Or Auto as appropriate
Error = (sender, args) =>
{
if (args.CurrentObject == args.ErrorContext.OriginalObject
&& args.ErrorContext.Error.GetBaseException() is JsonSerializationBinderException
)
{
args.ErrorContext.Handled = true;
}
},
// Other settings as required
};
使用这些设置,您将能够直接从流中反序列化 JSON 并跳过未知的 IDocument
类型,而无需加载和预处理 JToken
层次结构。
备注:
Json.NET可能会将一个应用程序抛出的异常包裹在多层异常中,所以必须使用
GetBaseException()
来判断是否抛出自定义异常。您可能不想在 序列化 时处理
JsonSerializationBinderException
,因为序列化时出现异常将表明您的活页夹中存在错误或缺少类型。通过只处理您的自定义异常,您可以确保不会吞噬其他错误引起的异常。例如。由格式错误的 JSON 文件引起的
JsonReaderException
永远不应被吞下,因为这样做可能会导致JsonTextReader
陷入无限循环。
演示 fiddle here.