2

I have a number of JSon files that I'm deserialising using

JsonSerializer serializer = new JsonSerializer(); t obj = (t)serializer.Deserialize(file, typeof(t));

to a collection of objects. They contain, amongst other things the following data. The number of arrays in "truthtable" is determined by the value of "gates"

"gates" : 1,
"truthtable" : [ false, true ]

and

"gates" : 2,
"truthtable" : [ [ false, false ], [ false, true ] ]

if I try to deserialise "truthtable" to the following property, example 1 fails.

public List<List<bool>>truthtable { get; set; }

Is there any way I can deserialise these two different types of truthtable to the same object? I've tried building a custom deserialiser, but Json sees both as "JsonToken.StartArray", so can't differentiate that way.

Ideally, I'd like to be able to deserialise both examples as if they were arrays of arrays of booleans.

Edit Should have mentioned, I cannot alter the way the Json files are created. I don't have access to their creation.

4 Answers4

2

This problem can be solved using a custom JsonConverter. The converter can read the number of gates and then populate the List<List<bool>> accordingly. If there is only one gate, it can wrap the single list in an outer list to make it work with your class.

Assuming the class that you are trying to deserialize into looks something like this:

class Chip
{
    public int Gates { get; set; }
    public List<List<bool>> TruthTable { get; set; }
}

then the converter might look something like this:

class ChipConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Chip));
    }

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

        Chip chip = new Chip();
        chip.Gates = (int)jo["gates"];
        JArray ja = (JArray)jo["truthtable"];

        if (chip.Gates == 1)
        {
            chip.TruthTable = new List<List<bool>>();
            chip.TruthTable.Add(ja.ToObject<List<bool>>());
        }
        else
        {
            chip.TruthTable = ja.ToObject<List<List<bool>>>();
        }

        return chip;
    }

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

To use the converter, create an instance and add it to the serializer's Converters collection before you deserialize:

serializer.Converters.Add(new ChipConverter());

Or if you prefer, you can annotate your class with a [JsonConverter] attribute instead:

[JsonConverter(typeof(ChipConverter))]
class Chip
{
    ...
}

Here's a demo showing the converter in action (note I used JsonConvert.DeserializeObject<T>() here instead of creating a JsonSerializer instance, but it works the same way):

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
            {
                ""gates"": 1,
                ""truthtable"": [ false, true ]
            },
            {
                ""gates"": 2,
                ""truthtable"": [ [ false, false ], [ false, true ] ]
            }
        ]";

        List<Chip> chips = JsonConvert.DeserializeObject<List<Chip>>(json,
                                                         new ChipConverter());

        foreach (Chip c in chips)
        {
            Console.WriteLine("gates: " + c.Gates);
            foreach (List<bool> list in c.TruthTable)
            {
                Console.WriteLine(string.Join(", ",
                    list.Select(b => b.ToString()).ToArray()));
            }
            Console.WriteLine();
        }
    }
}

Output:

gates: 1
False, True

gates: 2
False, False
False, True
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • I think this is exactly what I was after. Can I ask, just so I learn something. Is this reading the whole Json into a Jobject, then using the logic to populate a chip object? With this solution, if there were over 100 other properties in the chip class, and some chip objects that don't contain the gate property, would you need to manually assign each property with similar to `chip.Gates = (int)jo["gates"];` ? – Silmaril.SE Feb 24 '14 at 12:17
  • Yes, it is reading the whole JSON for the current chip into a `JObject`, and yes, you would need to populate all the properties of the chip you are interested in. If there are a lot of them, there are a couple of things you could do. (1) Use `jo.ToObject()` to populate the chip object first, then use the logic shown above to fix the truth table afterward. (2) Loop through the properties on the `JObject` and use reflection to set them on the chip. – Brian Rogers Feb 24 '14 at 15:06
  • Important note: if you choose option (1), you cannot use the `[JsonConverter]` attribute on the `Chip` class; you MUST pass the converter to the serializer (or else you'll end up with an infinite recursive loop as the converter tries to use itself to deserialize the chip). – Brian Rogers Feb 24 '14 at 15:17
0

Simple... change the method who generates truthtable (in case of gate == 1) to output array of array (or List>).

"gates" : 1, "truthtable" : [[ false,true ]]

Keep in mind, you have a type in your data contract,in this case the data contract expects an List> and you sent a wrong type (List<>). Can you wrote the code who generated "truthtable" in gate == 1 case?

PS. Sorry,i don´t speak english very well... =)

GilmarWSR
  • 1
  • 1
  • Ah, sorry, should have mentioned. I don't have access to the creation of the Json files. They're completely external. – Silmaril.SE Feb 21 '14 at 18:04
  • I that case, is not elegant but, you can change the type in a easy way and force truthtable to be Array> – GilmarWSR Feb 21 '14 at 18:38
0

Assuming you're using .NET 4.0 or above, you can deserialize the json into a Dynamic Object

string jsonData = ....; // fill your json data here
dynamic d = JsonConvert.DeserializeObject(jsonData);

then check the type of d.truthtable[0] and determine what to do when d.truthtable is an array or a nested array

if (d.truthtable != null && d.truthtable.Count > 0)
{
    if (d.truthtable[0].GetType() == typeof(Newtonsoft.Json.Linq.JValue))
    {
        // do something when truthtable is an array
    }
    else if (d.truthtable[0].GetType() == typeof(Newtonsoft.Json.Linq.JArray))
    {
        // do something when truthtable is a nested array
    }
}
ekad
  • 14,436
  • 26
  • 44
  • 46
  • If possible I'd rather avoid deserialising to a dynamic object. I'm deserialising in the region of 1000 Json files to the same object, as they're all very similar, its just a few of them have this problem. – Silmaril.SE Feb 21 '14 at 18:32
0

I´m assuming, this is a data contract conflict, and i´m suggest a workarrond... only think in performance...

In that case, is not elegant but, you can change the type in a easy way and force truthtable to be Array>..

String json = ...

        int idx = 0;
        while (idx >-1)
        {
            idx = json.Trim().IndexOf("\"truthtable\":[",idx);
            if (idx >-1 && json[idx + 15] != '[')
            {
                idx += 14;
                json = json.Insert(idx, "[");
                json = json.Insert(json.IndexOf("]", idx),"]");
            }
        }

This help you??

GilmarWSR
  • 1
  • 1