如何在反序列化 bad JSON 期间忽略异常?
How do I ignore exceptions during deserialization of bad JSON?
我正在消耗一个 API 应该 return 一个对象,比如
{
"some_object": {
"some_field": "some value"
}
}
当该对象为空时,我希望
{
"some_object": null
}
或
{
"some_object": {}
}
但是他们发给我的是
{
"some_object": []
}
...即使它从来都不是数组。
使用时
JsonSerializer.Deserialize<MyObject>(myJson, myOptions)
当 []
出现在预期 null
的位置时抛出异常。
我可以选择性地忽略这个异常吗?
我目前的处理方式是阅读 json 并在反序列化之前用正则表达式修复它。
我更喜欢使用System.Text.Json
,如果可能的话不引入其他依赖。
异常处理是我的一个小毛病。我有两篇其他人的文章,我 link 经常在这个问题上:
- https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/
- https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
我认为它们是必读的,并将它们作为讨论该主题的基础。
作为一般规则,永远不应忽略异常。充其量它们应该被捕获并发布。最坏的情况下,他们甚至不应该被抓住。粗心大意或过于激进,太容易引起后续问题,使调试无法进行。
话虽这么说,在这种情况下(反序列化)一些异常可以归类为外生异常或令人烦恼的异常。你抓到的是那种。使用 Vexing,您甚至可以吞下它们(就像 TryParse() 有点那样)。
通常你希望抓得越具体越好。但是,有时您会遇到范围很广的异常,它们没有像样的共同祖先,而是共享处理。幸运的是,我曾经写过这个尝试为卡在 1.1 上的人复制 TryParse():
//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).
bool TryParse(string input, out int output){
try{
output = int.Parse(input);
}
catch (Exception ex){
if(ex is ArgumentNullException ||
ex is FormatException ||
ex is OverflowException){
//these are the exceptions I am looking for. I will do my thing.
output = 0;
return false;
}
else{
//Not the exceptions I expect. Best to just let them go on their way.
throw;
}
}
//I am pretty sure the Exception replaces the return value in exception case.
//So this one will only be returned without any Exceptions, expected or unexpected
return true;
}
您可以使用 [OnError]
属性有条件地抑制与特定成员相关的异常。让我试着用一个例子来解释它。
代表JSON文件的例子class。它包含一个嵌套的 class SomeObject
。
public class MyObject
{
public int TemperatureCelsius { get; set; }
public SomeObject SomeObject { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
//You can check if exception is for a specific member then ignore it
if(errorContext.Member.ToString().CompareTo("SomeObject") == 0)
{
errorContext.Handled = true;
}
}
}
public class SomeObject
{
public int High { get; set; }
public int Low { get; set; }
}
如果样本 JSON stream/file 包含文本为:
{
"TemperatureCelsius": 25,
"SomeObject": []
}
然后处理并抑制异常,因为 SomeObject
成员引发了异常。 SomeObject
成员设置为 null
.
如果输入 JSON stream/file 包含文本为:
{
"TemperatureCelsius": 25,
"SomeObject":
{
"Low": 1,
"High": 1001
}
}
然后对象被正确序列化,SomeObject
代表期望值。
这是一个使用自定义 JsonConverter 和 Newtonsoft.Json 的解决方案。
如果 SomeObject
是数组,这会将 MyObject
中的 SomeObject
设置为 null。您可以 return SomeObject
的新实例,而不是 returning (T)Activator.CreateInstance(typeof(T))
.
public class ArrayToObjectConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
// this returns null (default(SomeObject) in your case)
// if you want a new instance return (T)Activator.CreateInstance(typeof(T)) instead
return default(T);
}
return token.ToObject<T>();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return true; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
请注意 Newtonsoft.Json 忽略 CanConvert
(因为 属性 装饰有 JsonConverter
属性)它假定它 可以 write 和 convert 所以不会调用这些方法(你可以 return false 或抛出 NotImplementedException 而不是它仍然会 serialize/deserialize)。
在您的模型中,使用 JsonConvert
属性修饰 some_object
。您的 class 可能看起来像这样:
public class MyObject
{
[JsonProperty("some_object")]
[JsonConverter(typeof(ArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
}
我知道您说过您更喜欢使用 System.Text.Json,但这可能对其他使用 Json.Net 的人有用。
更新:我确实使用 System.Text.Json 创建了一个 JsonConverter 解决方案,它是 。
此解决方案在 System.Text.Json 中使用自定义 JsonConverter
。
如果 some_object
是一个数组,那么它将 return 一个空对象(如果您愿意,也可以是 null),并且 不会抛出异常 。否则它将正确反序列化 json.
public class EmptyArrayToObjectConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
var rootElement = JsonDocument.ParseValue(ref reader);
// if its array return new instance or null
if (reader.TokenType == JsonTokenType.EndArray)
{
// return default(T); // if you want null value instead of new instance
return (T)Activator.CreateInstance(typeof(T));
}
else
{
var text = rootElement.RootElement.GetRawText();
return JsonSerializer.Deserialize<T>(text, options);
}
}
public override bool CanConvert(Type typeToConvert)
{
return true;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize<T>(writer, value, options);
}
}
用 JsonConverter
属性装饰您的 属性。您的 class 可能看起来像这样:
public class MyObject
{
[JsonPropertyAttribute("some_object")]
[JsonConverter(typeof(EmptyArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
...
}
上面的解决方案工作正常,我会为 .NET Core 3 及更高版本提供我的解决方案,这只是 reader,而不是编写器(不需要)。来源 json 存在错误,并给出了一个空数组,而它应该是 'null'。因此,此自定义转换器会执行更正工作。
所以:"myproperty":{"lahdidah": 1} 是 [] 而实际上应该是:"myproperty": null
注意,TrySkip,我们不需要吃假元素。
public sealed class JsonElementOrArrayFixerConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
reader.TrySkip();
return default;
}
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
我正在消耗一个 API 应该 return 一个对象,比如
{
"some_object": {
"some_field": "some value"
}
}
当该对象为空时,我希望
{
"some_object": null
}
或
{
"some_object": {}
}
但是他们发给我的是
{
"some_object": []
}
...即使它从来都不是数组。
使用时
JsonSerializer.Deserialize<MyObject>(myJson, myOptions)
当 []
出现在预期 null
的位置时抛出异常。
我可以选择性地忽略这个异常吗?
我目前的处理方式是阅读 json 并在反序列化之前用正则表达式修复它。
我更喜欢使用System.Text.Json
,如果可能的话不引入其他依赖。
异常处理是我的一个小毛病。我有两篇其他人的文章,我 link 经常在这个问题上:
- https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/
- https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET
我认为它们是必读的,并将它们作为讨论该主题的基础。
作为一般规则,永远不应忽略异常。充其量它们应该被捕获并发布。最坏的情况下,他们甚至不应该被抓住。粗心大意或过于激进,太容易引起后续问题,使调试无法进行。
话虽这么说,在这种情况下(反序列化)一些异常可以归类为外生异常或令人烦恼的异常。你抓到的是那种。使用 Vexing,您甚至可以吞下它们(就像 TryParse() 有点那样)。
通常你希望抓得越具体越好。但是,有时您会遇到范围很广的异常,它们没有像样的共同祖先,而是共享处理。幸运的是,我曾经写过这个尝试为卡在 1.1 上的人复制 TryParse():
//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).
bool TryParse(string input, out int output){
try{
output = int.Parse(input);
}
catch (Exception ex){
if(ex is ArgumentNullException ||
ex is FormatException ||
ex is OverflowException){
//these are the exceptions I am looking for. I will do my thing.
output = 0;
return false;
}
else{
//Not the exceptions I expect. Best to just let them go on their way.
throw;
}
}
//I am pretty sure the Exception replaces the return value in exception case.
//So this one will only be returned without any Exceptions, expected or unexpected
return true;
}
您可以使用 [OnError]
属性有条件地抑制与特定成员相关的异常。让我试着用一个例子来解释它。
代表JSON文件的例子class。它包含一个嵌套的 class SomeObject
。
public class MyObject
{
public int TemperatureCelsius { get; set; }
public SomeObject SomeObject { get; set; }
[OnError]
internal void OnError(StreamingContext context, ErrorContext errorContext)
{
//You can check if exception is for a specific member then ignore it
if(errorContext.Member.ToString().CompareTo("SomeObject") == 0)
{
errorContext.Handled = true;
}
}
}
public class SomeObject
{
public int High { get; set; }
public int Low { get; set; }
}
如果样本 JSON stream/file 包含文本为:
{
"TemperatureCelsius": 25,
"SomeObject": []
}
然后处理并抑制异常,因为 SomeObject
成员引发了异常。 SomeObject
成员设置为 null
.
如果输入 JSON stream/file 包含文本为:
{
"TemperatureCelsius": 25,
"SomeObject":
{
"Low": 1,
"High": 1001
}
}
然后对象被正确序列化,SomeObject
代表期望值。
这是一个使用自定义 JsonConverter 和 Newtonsoft.Json 的解决方案。
如果 SomeObject
是数组,这会将 MyObject
中的 SomeObject
设置为 null。您可以 return SomeObject
的新实例,而不是 returning (T)Activator.CreateInstance(typeof(T))
.
public class ArrayToObjectConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
// this returns null (default(SomeObject) in your case)
// if you want a new instance return (T)Activator.CreateInstance(typeof(T)) instead
return default(T);
}
return token.ToObject<T>();
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return true; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
请注意 Newtonsoft.Json 忽略 CanConvert
(因为 属性 装饰有 JsonConverter
属性)它假定它 可以 write 和 convert 所以不会调用这些方法(你可以 return false 或抛出 NotImplementedException 而不是它仍然会 serialize/deserialize)。
在您的模型中,使用 JsonConvert
属性修饰 some_object
。您的 class 可能看起来像这样:
public class MyObject
{
[JsonProperty("some_object")]
[JsonConverter(typeof(ArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
}
我知道您说过您更喜欢使用 System.Text.Json,但这可能对其他使用 Json.Net 的人有用。
更新:我确实使用 System.Text.Json 创建了一个 JsonConverter 解决方案,它是
此解决方案在 System.Text.Json 中使用自定义 JsonConverter
。
如果 some_object
是一个数组,那么它将 return 一个空对象(如果您愿意,也可以是 null),并且 不会抛出异常 。否则它将正确反序列化 json.
public class EmptyArrayToObjectConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
var rootElement = JsonDocument.ParseValue(ref reader);
// if its array return new instance or null
if (reader.TokenType == JsonTokenType.EndArray)
{
// return default(T); // if you want null value instead of new instance
return (T)Activator.CreateInstance(typeof(T));
}
else
{
var text = rootElement.RootElement.GetRawText();
return JsonSerializer.Deserialize<T>(text, options);
}
}
public override bool CanConvert(Type typeToConvert)
{
return true;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize<T>(writer, value, options);
}
}
用 JsonConverter
属性装饰您的 属性。您的 class 可能看起来像这样:
public class MyObject
{
[JsonPropertyAttribute("some_object")]
[JsonConverter(typeof(EmptyArrayToObjectConverter<SomeObject>))]
public SomeObject SomeObject { get; set; }
...
}
上面的解决方案工作正常,我会为 .NET Core 3 及更高版本提供我的解决方案,这只是 reader,而不是编写器(不需要)。来源 json 存在错误,并给出了一个空数组,而它应该是 'null'。因此,此自定义转换器会执行更正工作。
所以:"myproperty":{"lahdidah": 1} 是 [] 而实际上应该是:"myproperty": null
注意,TrySkip,我们不需要吃假元素。
public sealed class JsonElementOrArrayFixerConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
reader.TrySkip();
return default;
}
return JsonSerializer.Deserialize<T>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}