Как игнорировать исключения при десериализации плохих JSON? - PullRequest
2 голосов
/ 01 февраля 2020

Я использую API, который должен возвращать объект, например

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

, когда этот объект равен нулю, я ожидал бы

{
    "some_object": null
}

или

{
    "some_object": {}
}

Но они посылают мне

{
    "some_object": []
}

... даже если это никогда не массив.

При использовании

JsonSerializer.Deserialize<MyObject>(myJson, myOptions)

выдается исключение когда появляется [], где ожидается null.

Могу ли я выборочно игнорировать это исключение?

Мой текущий способ обработки это прочитать json и исправить его с помощью регулярного выражения до десериализации.

Я предпочитаю использовать System.Text.Json и не вводить другие зависимости, если это возможно.

Ответы [ 4 ]

1 голос
/ 01 февраля 2020

В этом решении используется пользовательский JsonConverter в System.Text. Json.

Если some_object является массивом, он вернет пустой объект (или ноль, если вы предпочитаете), и без исключения будет выдано . В противном случае он будет правильно десериализовать 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; }

    ...
}
1 голос
/ 01 февраля 2020

Атрибут [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 представляет ожидаемый ценность.

1 голос
/ 01 февраля 2020

Вот решение с использованием пользовательского JsonConverter и Newtonsoft. Json.

Это установит SomeObject в null в MyObject, если это массив. Вместо этого вы можете вернуть новый экземпляр SomeObject, вернув (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). предполагается, что может записывать и преобразовывать, поэтому не вызывает эти методы (вместо этого вы можете вернуть false или вызвать исключение NotImplementedException, и оно все равно будет сериализовать / десериализовать).

В вашей модели декорировать some_object с атрибутом JsonConvert. Ваш класс может выглядеть примерно так:

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

Я знаю, вы сказали, что предпочитаете использовать System.Text. Json, но это может быть полезно для других, использующих Json. Net.

Обновление: я создал решение JsonConverter с использованием System.Text. Json и оно здесь .

0 голосов
/ 01 февраля 2020

Исключением является моя любимая мозоль. И у меня есть две статьи от других людей, которые я часто связываю по теме:

Я считаю их обязательными для прочтения и использую их как основу для любого обсуждения топи c.

Как правило, исключение никогда не следует игнорировать. В лучшем случае они должны быть пойманы и опубликованы. В худшем случае их даже не поймают. Слишком легко вызвать проблемы с последующей работой и сделать невозможной отладку, чтобы быть небрежным или чрезмерно агрессивным.

При этом в данном случае (десериализация) некоторые исключения могут быть классифицированы как экзогенное или излишнее исключение. Какие вы ловите. А с Vexing вы можете даже проглотить их (как это делает TryParse ()).

Обычно вы хотите поймать как можно точнее c. Однако иногда вы получаете очень широкий спектр исключений без приличных общих предков, но с общей обработкой. К счастью, я однажды написал эту попытку реплицировать TryParse () для кого-то, кто застрял на 1.1:

//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;
}
...