0

I'm trying to handle deserialization error, but even if I'm able to reach error handling function and set Handled property to true then error is throwing to the main function.

Models:

public class PriceValidity
{
    public Date EndDate { get; set; }

    public Date StartDate { get; set; }

    [OnError]
    internal void OnError(StreamingContext context, ErrorContext errorContext)
    {
        errorContext.Handled = true;
    }
}

public class Date
{
    [JsonProperty("$date")]
    public DateTime Value { get; set; }
}

Calling deserializer:

private void ParseMessage<T>(string message) where T: new()
{
    var result = new T();
    var jsonSerializer = new Newtonsoft.Json.JsonSerializer();

    using (var reader = new StringReader(message))
    using (var jsonReader = new JsonTextReader(reader))
    {
        result = jsonSerializer.Deserialize<T>(jsonReader);
    };
}

JSON:

{  
   "StartDate":{  
      "$date":"2018-05-07T00:00:00.000Z"
   },
   "EndDate":{  
      "$date":{  
         "$numberLong":"253402214400000"
      }
   }
}

Error:

After parsing a value an unexpected character was encountered: :. Path 'EndDate.$date',

I don't want to handle also $numberLong scenario but just skip it.

Cœur
  • 37,241
  • 25
  • 195
  • 267
BąQ
  • 296
  • 1
  • 3
  • 13
  • Have a look at https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm to understand how to use `OnError` properly. – Chetan Oct 12 '18 at 10:15
  • @ChetanRanpariya - I can't find anything in that question that differs from the docs you linked. Can you help? – Christoph Lütjen Oct 12 '18 at 10:19
  • 1
    In docs there is wrote: "In this example accessing the Roles property will throw an exception when no roles have been set. The HandleError method will set the error when serializing Roles as handled and allow Json.NET to continue serializing the class." So if I good understood afer setting Handled to true, should go deserialization further. Or am I wrong ? – BąQ Oct 12 '18 at 10:23
  • 2
    My suggestion would be use the first approach of using OnError as part of `JsonSerializerSettings` instead of having `OnError` in the entity class itself. Having `OnError` in entity class is useful when there is a change of exception being thrown from the Entity class itself as explained for the `Roles` property of the `PersonError` class in the link I shared. For this question of OP, the exception is thrown by the NewtonSoft library... which should be handled by having `OnError` as part of `JsonSerializerSettings` – Chetan Oct 12 '18 at 10:24
  • Thanks for explanation. So my assumptions were wrong. – BąQ Oct 12 '18 at 10:31
  • @ChetanRanpariya - to be honest, for me this feels like a bug. The error triggers OnError will be marked as "handled" but still leads to an exception. – Christoph Lütjen Oct 12 '18 at 10:31
  • 1
    Tried to reproduce the usecase. https://dotnetfiddle.net/HEh4qT The error being handled in OnError inside the class and at the JSON serialization error are different. So even though one error is handled inside the class the other one being thrown by the library.. – Chetan Oct 12 '18 at 10:43

2 Answers2

1

Not an answer but a workaround: Move the error handling to serializer options:

    private T ParseMessage<T>(string message) where T : new() => 
        JsonConvert.DeserializeObject<T>(message, new JsonSerializerSettings
    {
        Error = (object sender, ErrorEventArgs args) => { args.ErrorContext.Handled = true; }
    });

From Chetan Ranpariya's comment:

Having OnError in entity class is useful when there is a change of exception being thrown from the Entity class itself as explained for the Roles property of the PersonError class here: https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm

Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
  • Unfortunatelly it doesn't solve problem. Currently I'm not obtaining exception, but in this scenario result is null. So for me it looks like bug in newtonsoft... :( – BąQ Oct 12 '18 at 12:05
1

Finally I found solution. To handle such scenario I've had to create Json Converter.

public class JMSDateTimeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            JToken token = JToken.Load(reader);

            if (token.Type == JTokenType.Object && GetAllChildresnCount(token) == 2)
            {
                return token.ToObject(objectType);
            }
            else
            {
                return null;
            }
        }
        catch (Exception ex)
        {
            return null;
        }
        finally
        {
        }
    }

    private int GetAllChildresnCount(JToken token)
    {
        var container = token as JContainer;

        if (container == null)
        {
            return 0;
        }

        var count = container.Count;

        foreach (JToken subToken in container)
        {
           count += GetAllChildresnCount(subToken);
        }

        return count;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

Now works fine.

BąQ
  • 296
  • 1
  • 3
  • 13
  • Using a custom `JsonConverter` is a good solution because 1) Errors in the JSON file syntax (e.g. a truncated file) will still get thrown via the call to `JToken.Load(reader)` while errors in deserialization will not; 2) The built-in error handling in Json.NET is stated to be [*very flakey*](https://github.com/JamesNK/Newtonsoft.Json/issues/1580#issuecomment-358546723) by Newtonsoft. You could override `CanWrite` and return `false` if you don't want to implement `WriteJson()`, see [How to use default serialization in a custom JsonConverter](https://stackoverflow.com/a/29616648/3744182). – dbc Oct 20 '18 at 20:39