SJaka SJaka - 1 year ago 153
C# Question

How to parse a JSON array without curly braces using Json.Net in C#?

I've a valid JSON object that has a JSON array in it. The JSON array doesn't have curly braces and contains a comma separated list of mixed type in it. It looks like this:

{
"ID": 17,
"Days": 979,
"Start_Date": "10/13/2012",
"End_Date": "11/12/2012",
"State": "",
"Page": 1,
"Test": "Valid",
"ROWS": [
[441210, "A", "12/31/2009", "OK", "Done", "KELLEY and Co'", "12/31/2009", "06/29/2010", "TEXAS", "Lawyers", 6, "", "<img src=\"/includes/images/Icon_done.gif\" border=\"0\" alt=\"Done\" title=\"Done\" />"],
[441151, "B", "12/31/2009", "No", "In-process", "Sage & Co", "12/31/2009", "06/29/2010", "CALIFORNIA", "Realtor", 6, "", "<img src=\"/includes/images/Icon_InProcess.gif\" border=\"0\" alt=\"In Process\" title=\"In Process\" />"]
]
}


I've created a class to reflect the JSON structure, having a List for the complex array:

class myModel
{
public int ID { get; set; }
public int Days { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string State { get; set; }
public string Page { get; set; }
public string Test { get; set; }
List<ChildModel> Rows { get; set; }
}


I've tested it with a List of a List too:

List<List<ChildModel>> Rows { get; set; }


And the child model like this:

class ChildModel
{
public int ID { get; set; }
public string StatusId { get; set; }
public DateTime ContactDate { get; set; }
public string State { get; set; }
public string Status { get; set; }
public string CustomerName { get; set; }
public DateTime WorkStartDate { get; set; }
public DateTime WorkEndDate { get; set; }
public string Territory { get; set; }
public string CustType { get; set; }
public int JobOrder { get; set; }
public string Filler { get; set; }
public string Link { get; set; }
}


In my program.cs file, I'm deserializing it like this:

using (StreamReader r = new StreamReader(@"D:\01.json"))
{

myModel items = JsonConvert.DeserializeObject<myModel>(r.ReadToEnd());

}


When I run this program, the child object (Rows) is always
null
. What am I doing wrong?

Answer Source

Json.Net does not have a facility to automatically map an array into a class. To do so you need a custom JsonConverter. Here is a generic converter that should work for you. It uses a custom [JsonArrayIndex] attribute to identify which properties in the class correspond to which indexes in the array. This will allow you to easily update your model if the JSON changes. Also, you can safely omit properties from your class that you don't need, such as Filler.

Here is the code:

public class JsonArrayIndexAttribute : Attribute
{
    public int Index { get; private set; }
    public JsonArrayIndexAttribute(int index)
    {
        Index = index;
    }
}

public class ArrayToObjectConverter<T> : JsonConverter where T : class, new()
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(T);
    }

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

        var propsByIndex = typeof(T).GetProperties()
            .Where(p => p.CanRead && p.CanWrite && p.GetCustomAttribute<JsonArrayIndexAttribute>() != null)
            .ToDictionary(p => p.GetCustomAttribute<JsonArrayIndexAttribute>().Index);

        JObject obj = new JObject(array
            .Select((jt, i) =>
            {
                PropertyInfo prop;
                return propsByIndex.TryGetValue(i, out prop) ? new JProperty(prop.Name, jt) : null;
            })
            .Where(jp => jp != null)
        );

        T target = new T();
        serializer.Populate(obj.CreateReader(), target);

        return target;
    }

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

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

To use the converter, you need to mark up your ChildModel class as shown below:

[JsonConverter(typeof(ArrayToObjectConverter<ChildModel>))]
class ChildModel
{
    [JsonArrayIndex(0)]
    public int ID { get; set; }
    [JsonArrayIndex(1)]
    public string StatusId { get; set; }
    [JsonArrayIndex(2)]
    public DateTime ContactDate { get; set; }
    [JsonArrayIndex(3)]
    public string State { get; set; }
    [JsonArrayIndex(4)]
    public string Status { get; set; }
    [JsonArrayIndex(5)]
    public string CustomerName { get; set; }
    [JsonArrayIndex(6)]
    public DateTime WorkStartDate { get; set; }
    [JsonArrayIndex(7)]
    public DateTime WorkEndDate { get; set; }
    [JsonArrayIndex(8)]
    public string Territory { get; set; }
    [JsonArrayIndex(9)]
    public string CustType { get; set; }
    [JsonArrayIndex(10)]
    public int JobOrder { get; set; }
    [JsonArrayIndex(12)]
    public string Link { get; set; }
}

Then just deserialize as usual and it should work as you wanted. Here is a demo: https://dotnetfiddle.net/n3oE3L

Note: I did not implement WriteJson, so if you serialize your model back to JSON, it will not serialize back to the array format; instead it will use the default object serialization.

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