使用TypeNameHandling.All
对列表进行反序列化时,如果其中一项的类型名称空间缺失(序列化后删除),则会导致Error resolving type specified in JSON
错误。
我希望忽略这些项目,而把其余的留在后面。
JsonSerializerSettings
中的Error = (sender, args) => { args.ErrorContext.Handled = true; }
做我正在寻找的,但当然会捕获所有错误。
是否有一种更干净的方法来做到这一点,也许是通过我错过的序列化器设置?
您可以在SerializationErrorCallback
中使用ErrorContext
的以下属性来限制要处理和忽略的错误类型:
-
ErrorEventArgs.ErrorContext.OriginalObject
: this 获取导致错误的原始对象。当由于类型名无效而无法构造列表项时,OriginalObject
将是列表本身。使用此属性,您可以检查
OriginalObject
是否为T
的IList<T>
。 -
ErrorEventArgs.CurrentObject
。获取正在引发错误事件的当前对象。异常会出现在序列化调用堆栈中,每个级别的对象都可以尝试处理该错误。您将希望在最低级别处理它,当
CurrentObject == ErrorContext.OriginalObject
. -
ErrorEventArgs.ErrorContext.Error
-获得实际抛出的异常。你只需要处理由序列化绑定器抛出的异常。
现在,如何检测和捕获由于类型名称绑定失败而导致的异常呢?结果是Json。NET的DefaultSerializationBinder
在无法加载类型时抛出JsonSerializationException
。但是,在许多其他情况下也可能抛出相同的异常,包括格式错误的JSON文件。因此,引入一个ISerializationBinder
装饰器来捕获和捕获来自默认JSON绑定器的异常,并将它们打包成特定的异常类型:
public class JsonSerializationBinder : ISerializationBinder
{
readonly ISerializationBinder binder;
public JsonSerializationBinder(ISerializationBinder binder)
{
if (binder == null)
throw new ArgumentNullException();
this.binder = binder;
}
public Type BindToType(string assemblyName, string typeName)
{
try
{
return binder.BindToType(assemblyName, typeName);
}
catch (Exception ex)
{
throw new JsonSerializationBinderException(ex.Message, ex);
}
}
public void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
binder.BindToName(serializedType, out assemblyName, out typeName);
}
}
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) { }
}
进一步,在代码Json的更高级别。NET将JsonSerializationBinderException
打包到另一个JsonSerializationException
中,因此在决定是否处理异常时,有必要查看内部异常,以查找必要类型的异常。下面的设置可以完成这项工作:
var settings = new JsonSerializerSettings
{
SerializationBinder = new JsonSerializationBinder(new DefaultSerializationBinder()),
TypeNameHandling = TypeNameHandling.All, // Or Auto or Objects as appropriate
Error = (sender, args) =>
{
if (args.CurrentObject == args.ErrorContext.OriginalObject
&& args.ErrorContext.Error.InnerExceptionsAndSelf().OfType<JsonSerializationBinderException>().Any()
&& args.ErrorContext.OriginalObject.GetType().GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IList<>)))
{
args.ErrorContext.Handled = true;
}
},
};
使用扩展方法:
public static class ExceptionExtensions
{
public static IEnumerable<Exception> InnerExceptionsAndSelf(this Exception ex)
{
while (ex != null)
{
yield return ex;
ex = ex.InnerException;
}
}
}
在这里演示小提琴#1。
注意,ISerializationBinder
是在Json中引入的。净10.0.1。在较早的版本中,包装器必须继承SerializationBinder
并在JsonSerializerSettings.Binder
中设置。
我也遇到过类似的问题,这与反序列化时缺少type有关。我遵循了@dbc建议的解决方案CustomSerializationBinder。
但这只适用于当你的missingType是在List。如果你的Dictionary对象是missingType,这个方法将不起作用。
//This works
{
"nodeList": [
{
"$type": "Leopotam.Ecs.BT.BTRuntimeAction, Assembly-CSharp",
"taskList": []
},
{
"$type": "Leopotam.Ecs.LancerGirl.BTTask.AlterResult, Assembly-CSharp", //Missing
"alterResult": 0
}
]
}
//This will not work
{
"nodeDic": {
"0": {
"$type": "Leopotam.Ecs.BT.BTRuntimeSequence, Assembly-CSharp",
"id": 0
},
"1": {
"$type": "Leopotam.Ecs.BT.BTRuntimeAction, Assembly-CSharp", // Missing
"taskList": []
}
}
}
如果字典中缺少BTRuntimeAction类型,可以跳过错误代码'args.ErrorContext.Handled = true;'然后会出现下一个错误,提示"解析错误与错误的符号";无法解析taskList"元素。Json应该忽略missingType的整个section字符串,但它只忽略一行,并解析下一行,导致错误。
所以…使用list