How to handle error in Json.Net parsing

2019-05-05 18:03发布

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条回答
走好不送
2楼-- · 2019-05-05 18:13

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
查看更多
登录 后发表回答