简体   繁体   English

如何在反序列化错误 JSON 期间忽略异常?

[英]How do I ignore exceptions during deserialization of bad JSON?

I am consuming an API that is supposed to return an object, like我正在使用一个应该返回一个对象的 API,比如

{
    "some_object": { 
        "some_field": "some value" 
    }
}

when that object is null, I would expect当该对象为空时,我希望

{
    "some_object": null
}

or或者

{
    "some_object": {}
}

But what they send me is但他们寄给我的是

{
    "some_object": []
}

...even though it's never an array. ...即使它从来都不是一个数组。

When using使用时

JsonSerializer.Deserialize<MyObject>(myJson, myOptions)

an exception is thrown when [] appears where null is expected.[]出现在预期为null位置时,将引发异常。

Can I selectively ignore this exception?我可以选择性地忽略这个异常吗?

My current way of handling this is to read the json and fix it with a regex before deserialization.我目前处理这个问题的方法是在反序列化之前读取 json 并使用正则表达式修复它。

I prefer to use System.Text.Json , and not introduce other dependencies, if possible.如果可能,我更喜欢使用System.Text.Json ,而不是引入其他依赖项。

This solution uses a custom JsonConverter in System.Text.Json .此解决方案在System.Text.Json 中使用自定义JsonConverter

If some_object is an array then it will return an empty object (or null if you prefer), and no exception will be thrown .如果some_object是一个数组,那么它将返回一个空对象(或 null,如果您愿意),并且不会抛出异常 Otherwise it will correctly deserialize the json.否则它将正确反序列化 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);
    }
}

Decorate your property with the JsonConverter attribute.使用JsonConverter属性装饰您的属性。 Your class might look something like this:你的类可能看起来像这样:

public class MyObject
{
    [JsonPropertyAttribute("some_object")]
    [JsonConverter(typeof(EmptyArrayToObjectConverter<SomeObject>))]
    public SomeObject SomeObject { get; set; }

    ...
}

You can use [OnError] attribute to conditionally suppress exception related with a particular member.您可以使用[OnError]属性有条件地抑制与特定成员相关的异常。 Let me try to explain it with an example.让我试着用一个例子来解释它。

The example class which represents JSON file.表示 JSON 文件的示例类。 It contains a nested class SomeObject .它包含一个嵌套类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; }
}

If sample JSON stream/file contains text as:如果示例 JSON 流/文件包含以下文本:

{
  "TemperatureCelsius": 25,
  "SomeObject": []
}

then exception is handled and suppressed as exception is raised for SomeObject member.然后在为SomeObject成员引发异常时处理和抑制异常。 The SomeObject member is set as null . SomeObject成员设置为null

If input JSON stream/file contains text as:如果输入 JSON 流/文件包含以下文本:

{
  "TemperatureCelsius": 25,
  "SomeObject":
  {
    "Low": 1,
    "High": 1001
  }
}

then object is serialized properly with SomeObject representing expected value.然后对象被正确序列化, SomeObject表示预期值。

Here is a solution using a custom JsonConverter and Newtonsoft.Json .这是使用自定义 JsonConverter 和Newtonsoft.Json的解决方案。

This will set SomeObject to null in MyObject if it is an array.如果它是一个数组,这将在MyObject中将SomeObject设置为 null。 You can return a new instance of SomeObject instead by returning (T)Activator.CreateInstance(typeof(T)) .您可以通过返回(T)Activator.CreateInstance(typeof(T))来返回SomeObject的新实例。

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);
    }
}

Note that Newtonsoft.Json ignores CanConvert (since the property is decorated with JsonConverter attribute) it assumes it can write and convert so does not call these methods (you could return false or throw NotImplementedException instead and it will still serialize/deserialize).请注意,Newtonsoft.Json 忽略CanConvert (因为该属性是用JsonConverter属性修饰的),它假定它可以编写和转换,因此不会调用这些方法(您可以返回 false 或抛出 NotImplementedException 并且它仍将序列化/反序列化)。

In your model, decorate some_object with the JsonConvert attribute.在您的模型中,使用JsonConvert属性装饰some_object Your class might look something like this:你的类可能看起来像这样:

public class MyObject
{
    [JsonProperty("some_object")]
    [JsonConverter(typeof(ArrayToObjectConverter<SomeObject>))]
    public SomeObject SomeObject { get; set; }
}

I know you said you'd prefer to use System.Text.Json but this might be useful for others using Json.Net.我知道你说过你更喜欢使用 System.Text.Json 但这可能对其他使用 Json.Net 的人有用。

Update: I did create a JsonConverter solution using System.Text.Json and it is here .更新:我确实使用 System.Text.Json 创建了一个 JsonConverter 解决方案,它就在这里

Exception handling is a pet peeve of mine.异常处理是我的一大烦恼。 And I have two articles from other people that I link often on the mater:我有两篇来自其他人的文章,我经常在这个网站上链接:

I consider them required reading and use them as basis of any discussion on the topic.我认为它们是必读的,并将它们用作有关该主题的任何讨论的基础。

As a general rule, Exception should never be ignored.作为一般规则,不应忽略异常。 At best they should be caught and published.充其量应该被抓住并发表。 At worst, they should not even be caught.在最坏的情况下,他们甚至不应该被抓住。 It is too easy to cause followup issues and make debugging impossible to be careless or overly agressive.很容易导致后续问题并使调试变得粗心或过于激进。

That being said, in this case (deserialisation) some Exceptions could be classified as either a Exogenous or Vexing Exception.话虽如此,在这种情况下(反序列化),一些异常可以归类为外生异常或令人烦恼的异常。 Wich are the kind you catch.这是你抓到的那种。 And with Vexing, you might even swallow them (like TryParse() kinda does).使用 Vexing,你甚至可能吞下它们(就像 TryParse() 那样)。

Usually you want to catch as specific as possible.通常你想捕捉尽可能具体的。 Sometimes however you got a very wide range of Exceptions with no decent common ancestors, but shared handling.然而,有时您会遇到非常广泛的异常,但没有像样的共同祖先,但共享处理。 Luckily I once wrote this attempt to replicate TryParse() for someone stuck on 1.1:幸运的是,我曾经写过这个尝试为坚持 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM