5

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": []
                },
                ...
Carsten
  • 4,204
  • 4
  • 32
  • 49
  • if you store the original json string along with the exception you get somewhere, I am sure you can easily solve your problem. If not, post them here. – L.B Feb 03 '14 at 02:14
  • @L.B The strings are too big to post (1 to several MB) and the data structure is complex - basically a tree where the tree nodes have multiple attributes, one of which is `name` which should be a string. I don't think there is a "easy fix" which does not involve the error handler of the parser. – Carsten Feb 03 '14 at 02:26
  • 2
    Maybe there is still an easy fix, but what can we say blindly without knowing the json, your model and the *unexpected* json. How about posting a self-contained, small, compilable code to reproduce your probem? – L.B Feb 03 '14 at 02:31
  • @L.B Simplified example added – Carsten Feb 04 '14 at 00:54

1 Answers1

3

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
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • I usually get correct JSON files to parse, i.e. they only have a string as expected in `name`. Occasionally, I however get an incorrect (incorrect = still valid JSON, but not correct according to the API specs) JSON file of the form in the example, i.e. they have a serialized object instead of a string. The example JSON represents a corrupted JSON file not the normal expected on (which parses fine with the class in the example). My question is how to handle the occasional incorrect JSON file - ideally with the Json.Net error handler mechanism. – Carsten Feb 04 '14 at 03:36
  • OK, can also you post some of the "correct" JSON then, so we can see the difference? Also, can you explain how to construct the correct name string from the incorrect JSON? Is is just a matter of reading the name from the inner object and moving it to the outer? – Brian Rogers Feb 04 '14 at 03:52
  • "correct" JSON added. Yes, the correct name could be easily extracted from the inner object. What I hope I could do in the error handler would be: 1) Ask the parser what it couldn't parse (the string with the inner object) 2) parse that inner object separately using a class like you `Name`. 3) Read the `name` value from the parsed inner object and pass it back to the outer parser or otherwise set it in the outer object currently being parsed. – Carsten Feb 04 '14 at 04:43
  • I've revised my answer. Hope this helps. – Brian Rogers Feb 04 '14 at 06:22