How to handle error in Json.Net parsing

↘锁芯ラ 提交于 2019-12-07 07:55:04

问题


I'm using Json.Net for Json deserialization. On occasion the Json string I read is not correct (which I can't fix since I don't produce it). In particular, at one specific place, where there should be a string, sometimes there is a serialized object instead. Json.Net then complains, not surprisingly, about finding an object where it expected a string.

I found I can intercept this by using Error in JsonSerializerSettings and also make Json.Net ignore the problem by setting ErrorContext.Handled. But I want to do even more. If I could look at the serialised object I could figure out what the string should be and in theory supply the correct answer. In practice I can't figure our how to do so. Especially, in the error handler:

  • How do I access the string that the parser tripped up on (Note that the parser can successfully continue if ErrorContext.Handled is set, so it can determine start and end of the problem string correctly)?
  • How do I supply the correct string back to the parser, or alternatively get access to the currently parsed object so I can set the value manually?

[Edit] As requested a simplified example:

The incorrect Json string I get to parse:

{
    "id": 2623,
    "name": {
        "a": 39,
        "b": 0.49053320637463277,
        "c": "cai5z+A=",
        "name": "22"
    },
    "children": [
        {
            "id": 3742,
            "name": {
                "a": 37,
                "b": 0.19319664789046936,
                "c": "Me/KKPY=",
                "name": "50"
            },
            "children": [
                {
                    "id": 1551,
                    "name": {
                        "a": 47,
                        "b": 0.6935373953047849,
                        "c": "qkGkMwY=",
                        "name": "9"
                    },
                    "children": []
                },
                {
                    "id": 4087,
                    "name": {
                        "a": 5,
                        "b": 0.42905938319352427,
                        "c": "VQ+yH6o=",
                        "name": "84"
                    },
                    "children": []
                },
                {
                    "id": 614,
                    "name": {
                        "a": 19,
                        "b": 0.7610801005554758,
                        "c": "czjTK1s=",
                        "name": "11"
                    },
                    "children": []
                }
            ]
        },
        {
            "id": 3382,
            "name": {
                "a": 9,
                "b": 0.36416331043660793,
                "c": "lnoHrd0=",
                "name": "59"
            },
            "children": [
                {
                    "id": 4354,
                    "name": {
                        "a": 17,
                        "b": 0.8741648112769075,
                        "c": "CD2i2I0=",
                        "name": "24"
                    },
                    "children": []
                },
                {
                    "id": 2533,
                    "name": {
                        "a": 52,
                        "b": 0.8839575992356788,
                        "c": "BxFEzVI=",
                        "name": "60"
                    },
                    "children": []
                },
                {
                    "id": 5733,
                    "name": {
                        "a": 4,
                        "b": 0.7230552787534219,
                        "c": "Un7lJGM=",
                        "name": "30"
                    },
                    "children": []
                }
            ]
        },
        {
            "id": 9614,
            "name": {
                "a": 81,
                "b": 0.4015882813379114,
                "c": "dKgyRZk=",
                "name": "63"
            },
            "children": [
                {
                    "id": 7831,
                    "name": {
                        "a": 81,
                        "b": 0.2784254314743101,
                        "c": "xZur64o=",
                        "name": "94"
                    },
                    "children": []
                },
                {
                    "id": 6293,
                    "name": {
                        "a": 73,
                        "b": 0.32629523068959604,
                        "c": "lMkosP4=",
                        "name": "93"
                    },
                    "children": []
                },
                {
                    "id": 5253,
                    "name": {
                        "a": 13,
                        "b": 0.19240453242901923,
                        "c": "oOPZ3tA=",
                        "name": "5"
                    },
                    "children": []
                }
            ]
        }
    ]
}

And here to class to parse it into:

class Node
{
     [JsonProperty]
    int id;
     [JsonProperty]
    string name;
     [JsonProperty]
    List<Node> children;
}

As you can see it expects a string name but sometimes incorrectly gets a serialised object (which contains the string in question as a member). This only happens in some JSON strings but not others, so I can't just change the class definition of Node to match.

[Edit 2] A "correct" Json string according to my API would look like this:

{
    "id": 2623,
    "name": "22",
    "children": [
        {
            "id": 3742,
            "name": "50",
            "children": [
                {
                    "id": 1551,
                    "name": "9",
                    "children": []
                },
                {
                    "id": 4087,
                    "name":"84",
                    "children": []
                },
                ...

回答1:


Trying to detect errors after the fact and then reparse from the point of the error is going to be problematic, as you have seen. Fortunately, the problem you've described can be solved in a straightforward manner using a custom JsonConverter. The idea is to have the converter read the data into a temporary structure that can handle either form (object or string), query the type, then construct the Node from there.

Here is the code for the converter:

class NodeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Node));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);

        Node node = new Node();
        node.id = (int)jo["id"];

        JToken name = jo["name"];
        if (name.Type == JTokenType.String)
        {
            // The name is a string at the current level
            node.name = (string)name;
        }
        else
        {
            // The name is one level down inside an object
            node.name = (string)name["name"];
        }

        node.children = jo["children"].ToObject<List<Node>>(serializer);

        return node;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use the converter, add a [JsonConverter] attribute to your Node class like this:

[JsonConverter(typeof(NodeConverter))]
class Node
{
    public int id { get; set; }
    public string name { get; set; }
    public List<Node> children { get; set; }
}

Then you can deserialize as normal:

Node node = JsonConvert.DeserializeObject<Node>(json);

Here is a full demo showing the converter in action. For illustration purposes, I've created a new JSON string that contains a combination of the "good" and "bad" nodes you described in your question.

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""id"": 2623,
            ""name"": {
                ""a"": 39,
                ""b"": 0.49053320637463277,
                ""c"": ""cai5z+A="",
                ""name"": ""22""
            },
            ""children"": [
                {
                    ""id"": 3741,
                    ""name"": ""50"",
                    ""children"": [
                        {
                            ""id"": 1550,
                            ""name"": ""9"",
                            ""children"": []
                        },
                        {
                            ""id"": 4088,
                            ""name"": {
                                ""a"": 5,
                                ""b"": 0.42905938319352427,
                                ""c"": ""VQ+yH6o="",
                                ""name"": ""85""
                            },
                            ""children"": []
                        }
                    ]
                },
                {
                    ""id"": 3742,
                    ""name"": {
                        ""a"": 37,
                        ""b"": 0.19319664789046936,
                        ""c"": ""Me/KKPY="",
                        ""name"": ""51""
                    },
                    ""children"": [
                        {
                            ""id"": 1551,
                            ""name"": {
                                ""a"": 47,
                                ""b"": 0.6935373953047849,
                                ""c"": ""qkGkMwY="",
                                ""name"": ""10""
                            },
                            ""children"": []
                        },
                        {
                            ""id"": 4087,
                            ""name"": ""84"",
                            ""children"": []
                        }
                    ]
                }
            ]
        }";

        Node node = JsonConvert.DeserializeObject<Node>(json);
        DumpNode(node, "");
    }

    private static void DumpNode(Node node, string indent)
    {
        Console.WriteLine(indent + "id = " + node.id + ", name = " + node.name);
        foreach(Node child in node.children)
        {
            DumpNode(child, indent + "    ");
        }
    }
}

Output:

id = 2623, name = 22
    id = 3741, name = 50
        id = 1550, name = 9
        id = 4088, name = 85
    id = 3742, name = 51
        id = 1551, name = 10
        id = 4087, name = 84


来源:https://stackoverflow.com/questions/21518756/how-to-handle-error-in-json-net-parsing

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