JsonSerializationException Parsing

耗尽温柔 提交于 2019-12-10 20:18:14

问题


I'm trying to wrap my application with some graceful exception handling. When my app fails to deserialize JSON, I get a JsonSerializationException that has a Message that looks like this:

Error converting value "[IncorrectDataType]" to type 'ApexFramework.Enums+DataTypes'. Path 'Layout[0].ElementContainer[0].ContainerDatatype', line 12, position 58.

From this exception message, I am interested in capturing and presenting in a clean manner the following:

  • I need the Index of the Element Container in Layout[] (this would be 0 from the above example)
  • I need the Index of the ContainerDataType in ElementContainer[] (this would be 0 from the above example)

I looked through the

JsonSerializationException

object and there is nothing that I grab with ease, so:

Can anyone confirm that there is no way to work with the JsonSerializationException to get the info I need in a clean manner?

If not, can anyone help me come up with the most elegant and efficient way to grab the info I need from the exception stack?


回答1:


You can use Json.NET's serialization error event handling functionality to get information about the exception in a somewhat more useful format.

When an exception during deserialization occurs, Json.NET catches and re-throws it at each level in the object hierarchy, giving each object an opportunity to handle the exception using an OnError method. While you don't want to handle the exception, you can take advantage of this to record details about where the exception occurred via the information provided in the ErrorEventArgs passed to the JsonSerializerSettings.Error event handler. For instance, the following code captures and reports the path at which the exception occurred, the member causing the exception, and the stack of objects being deserialized at when the error was encountered:

static bool TryDeserialize<TRootObject>(string jsonString, out TRootObject root)
{
    var errorStack = new Stack<Newtonsoft.Json.Serialization.ErrorEventArgs>();
    var settings = new JsonSerializerSettings
    {
        Converters = { new StringEnumConverter() },
        Error = (o, e) => errorStack.Push(e)
    };
    try
    {
        root = JsonConvert.DeserializeObject<TRootObject>(jsonString, settings);
        return true;
    }
    catch (JsonException ex)
    {
        var last = errorStack.Last();
        var member = last.ErrorContext.Member;
        var path = last.ErrorContext.Path;
        var objectsStack = String.Join(", ", errorStack
                                       .Where(e => e.CurrentObject != null)
                                       .Select(e => e.CurrentObject.ToString()));

        Console.WriteLine("Exception parsing JSON: ");
        Console.WriteLine(ex.Message);
        Console.WriteLine("Error context details: ");
        Console.WriteLine("   Path: {0}\n   Member: {1}\n   Object stack = {{{2}}}", 
                          path, member, objectsStack);
        root = default(TRootObject);
        return false;
    }
}

Then, if I attempt to deserialize JSON into the data model implied by your question, I get the following error messages:

Exception parsing JSON: 
Error converting value "[IncorrectDataType]" to type 'DataTypes'. Path 'Layout[0].ElementContainer[0].ContainerDatatype', line 6, position 56.
Error context details: 
   Path: Layout[0].ElementContainer[0].ContainerDatatype
   Member: ContainerDatatype
   Object stack = {RootObject, System.Collections.Generic.List`1[Layout], Layout, System.Collections.Generic.List`1[ElementContainer], ElementContainer}

As you can see, the information you need is more cleanly available, so that you will no longer need to parse the exception message. You will still need to parse the exception path to extract the indices, however the path syntax is standard JSONPath syntax and so is well-defined. You could model your path parsing code on any pre-existing JSONPath parser including Json.NET's own JPath.

Notes:

  • I don't particularly recommend handling the exception and continuing, since error handling in Json.NET is reported to be "very flaky" by Newtonsoft.

Sample working .Net fiddle here.




回答2:


I'm not sure if this is what you need, but you can use Json.NET Schema to validate your json string and get more decent validation error messages.

You can create a json schema (string) or validate an object using DataAnnotations.

You can find both packages in Nuget:

Install-Package Newtonsoft.Json.Schema -Version 3.0.10

Install-Package System.ComponentModel.Annotations -Version 4.5.0

Consider the following model:

public class Album
{
    [DisplayName("Album Id")]
    [Description("Album Id must be a positive integer")]
    [Range(1, Int32.MaxValue)]
    public int AlbumId { get; set; }

    [DisplayName("Artist Id")]
    [Description("Artist Id must be a positive integer")]
    [Range(1, Int32.MaxValue)]
    public int ArtistId { get; set; }

    [DisplayName("Title")]
    [Description("An Album Title is required")]
    [Required()]
    [StringLength(160)]
    public string Title { get; set; }

    [DisplayName("Album Price")]
    [Description("Album Price must be between 0.01 and 100.00")]
    [Range(0.01, 100.00)]
    public decimal Price { get; set; }
}

And the following class that will contain information about a validation error:

public class ValidationError
{
    public string Property { get; set; }
    public string Message { get; set; }
    public object CurrentValue { get; set; }
}

Deserializing and validating a json string:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.IO;

using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;

// json that contains properties with invalid values
string json = @"{
  ""AlbumId"": -10,
  ""ArtistId"": 1,
  ""Title"": null,
  ""Price"": 200.0
}";


JsonTextReader reader = new JsonTextReader(new StringReader(json));
JSchemaValidatingReader validatingReader = new JSchemaValidatingReader(reader)
{
    Schema = new JSchemaGenerator().Generate(typeof(Album))
};

var errors = new List<ValidationError>();
validatingReader.ValidationEventHandler += (o, a) => errors.Add(new ValidationError
{
    Property = a.ValidationError.Schema.Title,
    Message = a.ValidationError.Schema.Description,
    CurrentValue = a.ValidationError.Value
});

JsonSerializer serializer = new JsonSerializer();
Album album = serializer.Deserialize<Album>(validatingReader);

foreach (ValidationError error in errors)
{
    Debug.WriteLine($"Property: {error.Property}, Message: {error.Message}");
}

Output:

Property: Album Id, Message: Album Id must be a positive integer

Property: Title, Message: An Album Title is required

Property: Album Price, Message: Album Price must be between 0.01 and 100.00



来源:https://stackoverflow.com/questions/53130847/jsonserializationexception-parsing

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!