C# DataContractJsonSerializer fails when value can be an array or a single item
Asked Answered
G

3

5

I use the DataContractJsonSerializer to parse a json string into a object hierarchie. The json string looks like this:

{
    "groups": [
        {
            "attributes": [
                {
                    "sortOrder": "1",
                    "value": "A"
                },
                {
                    "sortOrder": "2",
                    "value": "B"
                }
            ]
        },
        {
            "attributes": {
                "sortOrder": "1",
                "value": "C"
            }
        }
    ]
}

As you can see the sub value of "attributes" can be an array or a single item. I found the code part where the problem occures:

[DataContract]
public class ItemGroup
{
    [DataMember(Name="attributes")]
    public List<DetailItem> Items  { get; set; }
}

This works for the first one but fails on the second one.

Has anyone an answer for this?

Thx

Gradus answered 4/10, 2011 at 12:9 Comment(2)
why is the json inconsistent with this? would fixing-at-source be possible?Trevelyan
How does it fail? What error?Gan
M
3

If you control how the JSON is created then make sure that attributes is an array even if it only contains one element. Then the second element will look like this and parse fine.

    {
        "attributes": [{
            "sortOrder": "1",
            "value": "C"
        }]
    }
Mcduffie answered 4/10, 2011 at 12:17 Comment(0)
H
3

As Daniel said, if you can control the creation of Json, it is better to continue that way. But if you can't, then you can use Json.Net library & the JsonObject class from this link to write some code like:

JObject o = (JObject)JsonConvert.DeserializeObject(input);
dynamic json = new JsonObject(o);
foreach (var x in json.groups)
{
      var attrs = x.attributes;
      if (attrs is JArray)
      {
           foreach (var y in attrs)
           {
               Console.WriteLine(y.value);
           }
      }
      else
      {
          Console.WriteLine(attrs.value);
      }
 }
Halfbeak answered 4/10, 2011 at 12:50 Comment(0)
I
1

I tried to get this working with DataContractJsonSerializer, JavaScriptSerializer, and JSON.Net and none would deserialize directly to an object successfully in all cases. I used a similar approach as L.B, using JSON.Net, although without the use of dynamics and the extra JsonObject class. Adapting my code to your scenario would look something like the following:

private List<ItemGroup> ParseItemGroupList(string input)
    {
        JObject json = JObject.Parse(input);

        List<ItemGroup> groups = new List<ItemGroup>();
        JArray gArray = json["groups"] as JArray;
        foreach (var gToken in gArray)
        {
            ItemGroup newGroup = new ItemGroup();
            JToken attrToken = gToken["attributes"] as JToken;
            if (attrToken is JArray)
            {
                newGroup.Items = attrToken.Children().Select(MapDetailItem()).ToList();
            }
            else
            {
                newGroup.Items = new List<DetailItem>() { MapDetailItem().Invoke(attrToken) };
            }

            groups.Add(newGroup);
        }

        return groups;
    }

    private static Func<JToken, DetailItem> MapDetailItem()
    {
        return json => new DetailItem
        {
            SortOrder = (string)json["sortOrder"],
            Value = (string)json["value"]
        };
    }

Hopefully, someone will add a setting for JSON.Net to allow it to force deserialization to a collection with a single item rather than throwing an exception. It's a shame that you have to do all of the parsing manually when there is only one small portion of the JSON that doesn't parse correctly automatically.

Insufficiency answered 17/2, 2012 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.