【问题标题】:How do I ignore exceptions during deserialization of bad JSON?如何在错误 JSON 的反序列化过程中忽略异常?
【发布时间】:2020-01-31 22:56:57
【问题描述】:

我正在使用一个应该返回对象的 API,例如

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

当该对象为空时,我希望

{
    "some_object": null
}

{
    "some_object": {}
}

但他们送我的是

{
    "some_object": []
}

...即使它从来都不是一个数组。

使用时

JsonSerializer.Deserialize<MyObject>(myJson, myOptions)

[] 出现在预期null 的位置时,将引发异常。

我可以选择性地忽略这个异常吗?

我目前的处理方式是在反序列化之前读取 json 并使用正则表达式对其进行修复。

如果可能的话,我更喜欢使用System.Text.Json,而不是引入其他依赖项。

【问题讨论】:

  • 发生异常时你想做什么?请注意,其他几个格式错误也可能发生异常。如果您想忽略异常并继续序列化,则可以使用[OnError] 属性。
  • 如果我可以将异常隔离到这个特定的东西上,那么吞下它是安全的。也许在 OnError() 里面我可以看到当前属性是否是有问题的,如果是,继续?
  • 你可以试试JsonConvert.DeserializeObject&lt;MyObject&gt;("some json string", new JsonSerializerSettings { Error = MyDeserializationErrorHandler });Newtonsoft Documentation
  • 您可以为您的班级创建一个自定义 JsonConverter,然后对您的属性进行JsonToken.StartArray 检查,然后从那里返回 null。 stackoverflow.com/questions/40439290/…
  • @Chris 我添加了一个基于[OnError] 属性的解决方案。看看吧。

标签: c# json asp.net-core system.text.json


【解决方案1】:

此解决方案在 System.Text.Json 中使用自定义 JsonConverter

如果some_object 是一个数组,那么它将返回一个空对象(如果您愿意,也可以返回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 属性装饰您的财产。你的班级可能看起来像这样:

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

    ...
}

【讨论】:

  • 这很棒,而且看起来很有效。但我想缺点是它不支持写作?我想无论如何我都必须这样做......
  • 如果我将var rootElement = JsonDocument.ParseValue(ref reader); 行移到实际使用它的else 块中,此解决方案将停止工作。所以我把它搬回来了。但这是为什么呢?将读者传递到ParseValue 会如何改变读者?
  • @Chris 是的,我没有实现作家,我不知道你是否需要它,而且......我昨晚感觉很懒!给我一点,我会想出一些东西给你。至于为什么它停止工作我不知道......我什至没有测试过。也许与读者只读/单向有关...不确定
  • 哦,不要懒惰。这些都不是您的义务,而且效果很好!
  • @Chris 实际上实现编写器非常容易,只需一行代码。我已经更新了我的答案(并重新命名了转换器,因为它确实转换了一个空数组)。我最初在 Newtonsoft.Json 中写(并发布)了一个答案,但是当我可以使用这个解决方案时将其删除。我将取消删除它,因为它可能对使用 Json.Net 的其他人有用
【解决方案2】:

您可以使用[OnError] 属性有条件地抑制与特定成员相关的异常。让我试着用一个例子来解释一下。

表示 JSON 文件的示例类。它包含一个嵌套类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 流/文件包含以下文本:

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

然后在为SomeObject 成员引发异常时处理和抑制异常。 SomeObject 成员设置为null

如果输入 JSON 流/文件包含如下文本:

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

然后对象被正确序列化,SomeObject 代表期望值。

【讨论】:

  • 此 [OnError] 属性存在于 Newtonsoft 库中。这不适用于 System.Text.Json,它在 asp.net 核心中默认使用。
  • 这对 System.Text.Json 没有用,但是,这对于使用 Newtonsoft.Json 寻找相同问题的 Newtonsoft 用户很有帮助
【解决方案3】:

这是一个使用自定义 JsonConverter 和 Newtonsoft.Json 的解决方案。

如果它是一个数组,这会将MyObject 中的SomeObject 设置为null。您可以通过返回 (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);
    }
}

请注意,Newtonsoft.Json 忽略 CanConvert(因为该属性用 JsonConverter 属性装饰)它假定它可以写入和转换,因此不调用这些方法(您可以返回 false 或而是抛出 NotImplementedException,它仍然会序列化/反序列化)。

在您的模型中,使用JsonConvert 属性装饰some_object。你的班级可能看起来像这样:

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

我知道您说过您更喜欢使用 System.Text.Json,但这可能对使用 Json.Net 的其他人有用。

更新:我确实使用 System.Text.Json 创建了一个 JsonConverter 解决方案,它是 here

【讨论】:

  • 拥有这个很好,因为无论如何我最终可能会使用 Json.Net。 System.Text.Json 尚不支持snake_case。
【解决方案4】:

异常处理是我最讨厌的事情。我有两篇来自其他人的文章,我经常在母校上链接:

我认为它们是必读的,并将它们用作任何关于该主题的讨论的基础。

作为一般规则,永远不应忽略异常。充其量他们应该被抓住并发表。在最坏的情况下,他们甚至不应该被抓住。太容易引起后续问题,使调试变得不可能粗心或过于激进。

话虽如此,在这种情况下(反序列化),一些异常可以归类为外生异常或令人烦恼的异常。你抓到的就是那种。使用 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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    • 2018-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-09
    相关资源
    最近更新 更多