Crag Crag -4 years ago 98
JSON Question

Remove nesting/arrays when deserializing JSON using JSON.Net

I'm running into some issues trying to deserialize some JSON from an API I'm working with. For some reason the API likes to wrap the data in extra layers and arrays when it isn't necessary.

{
"CustomData": [
{
"Wrapper": [
{
"OptionalDataSet1": [
{
"ItemA": "Basic string"
},
{
"ItemB": "Another basic string"
}
]
}
]
}
]
}


Using json2csharp I've gotten classes that work for the above example.

public class OptionalDataSet1
{
public string ItemA { get; set; }
public string ItemB { get; set; }
}

public class Wrapper
{
public List<OptionalDataSet1> OptionalDataSet1 { get; set; }
}

public class CustomData
{
public List<Wrapper> Wrapper { get; set; }
}

public class RootObject
{
public List<CustomData> CustomData { get; set; }
}


The issue I'm having is the "Wrapper" class is unneeded according to the API. It's always there but will be the only item in Custom Data. Furthermore, there will only ever be a single instance of "OptionalDataSet1" inside of the Wrapper. There are other "OptionalDataSets", but again, they will be unique per request.

Finally, the Wrapper deserializes TWO objects for "OptionalDataSet1", the first with ItemA's value, the second with ItemBs. There are other data sets that can have upwards of forty items available to them, I don't want to scan through forty instances of an object to find which one has the single data attribute I'm trying to find.

Should I massage the JSON string I'm receiving from the API before sending it off to be deserialized by removing the "Wrapper" and converting the List<> properties to singular instances, or is there another method I'm missing using JSON.Net to produce something like

RootObject.CustomData.OptionalDataSet1.ItemB


Instead of

RootObject.CustomData[0].Wrapper[0].OptionalDataSet[1].ItemB

Answer Source

Well, the solution ended up being to write my own JsonConverter as mentioned by dbc

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

    foreach (PropertyInfo prop in objectType.GetProperties()) {
        string jsonPath = prop.Name;
        JToken token = jo.SelectToken(jsonPath);

        if (jsonPath.Equals("OptionalDataSet1")) {
            var innerItems = jo.SelectToken("Wrapper[0]").SelectToken(jsonPath).Values();
            var finalItem = new JObject();
            foreach (var item in innerItems) {
                var tempItem = new JObject(item);
                finalItem.Merge(tempItem);
            }
        } else {
            token = jo.SelectToken(jsonPath).First();
        }

        if (token != null && token.Type != JTokenType.Null) {
            object value = token.ToObject(prop.PropertyType, serializer);
            prop.SetValue(targetObj, value, null);
        }
    }

    return targetObj;
}

With this custom converter, I was able to remove the need for the Wrapper class, and have single instances of OptionalDataSet1 and CustomData, and the single instance of OptionalDataSet1 has all of it's info correctly populated.

(code might be off, converted from VB.Net on the fly for this answer)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download