Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I select an object type to deserialize using Json.Net based on property values in the data

I need to deserialize json like this (it's as defined in DICOM - an international standard, so I can't change the format!)

{
....
"00080060": {
  "Value": [
    "US"
  ],
  "vr": "CS"
},
"00080070": {
  "Value": [
    "ACME Products"
  ],
  "vr": "LO"
},
"00080090": {
  "Value": [
    {
      "AlphabeticName": "Better^Make^U.^MD"
    }
  ],
  "vr": "PN"
},
"00081110": {
  "Value": [
    {
      "00080008": {
        "Value": [
          "XX_0",
          "OIU",
          null,
          "PPP"
        ],
        "vr": "CS"
      }
    },
    {},
    {
      "00080008": {
        "Value": [
          "XX_2",
          "OIU",
          null,
          "PPP"
        ],
        "vr": "CS"
      }
    }
  ],
  "vr": "SQ"
},

Each property (in a long list!) has a name (0008XXXX in example above), and has sub-properties "vr" and Value. In most cases, Value is simply an array of objects (string or number) and that's fine, but for 2 special cases (PN and SQ as above) it needs special handling, and in the case of SQ it is actually an array of the top level object again (which can be nested recursively ad infinitum...)

So - what I need is a simple method, during deserialization to inspect the "vr" value and to give json.net the type it should use for the associated "Value" - is there a simple means to do that? Of course, the fact that vr could come before or after Value (depending on remote implementation) might complicate things further...

I've looked at json.net's ContractResolver and JsonConverter mechanisms, but ContractResolver seems not to give me access to the data being read in order to allow to make the choice, and JsonConverter derived classes would seem to leave me having to re-implement most of what json.net is dong (otherwise so well!) for me already :-(

Am I missing some obvious and simple solution here?

like image 978
medconn Avatar asked Sep 04 '14 07:09

medconn


Video Answer


1 Answers

Ugh, this is a difficult format to work with. However, it should be possible to make sense of it using a custom JsonConverter. Using a JObject inside the converter will shield us from most of the heavy lifting. But first, we need to define a couple of classes to deserialize the data into.

The first class I'll call Node; this will contain the Value list and vr.

class Node
{
    public IList Value { get; set; }
    public string vr { get; set; }
}

The second class is needed for holding the items for the "PN" case.

class PnItem
{
    public string AlphabeticName { get; set; }
}

Here is the code for the converter. The converter can look at the vr property and use that information to create the correct type of list for the Value.

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.vr = (string)jo["vr"];
        if (node.vr == "PN")
        {
            node.Value = jo["Value"].ToObject<List<PnItem>>(serializer);
        }
        else if (node.vr == "SQ")
        {
            node.Value = jo["Value"].ToObject<List<Dictionary<string, Node>>>(serializer);
        }
        else
        {
            node.Value = jo["Value"].ToObject<List<string>>(serializer);
        }
        return node;
    }

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

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

Notice that for the "SQ" case we are deserializing a List<Dictionary<string, Node>>. This covers the recursive structure. Whenever Json.Net tries to deserialize a Node, it will call back into the converter. We use a Dictionary to handle the fact that the property names can vary (e.g. "00080070", "00080090", etc.). At the root, we must also deserialize to a Dictionary<string, Node> for the same reason.

So, to tie it all together, here is how you would deserialize your JSON:

var dict = JsonConvert.DeserializeObject<Dictionary<string, Node>>(json, 
                                                            new NodeConverter());

Here is a demo: https://dotnetfiddle.net/hsFlxU

like image 177
Brian Rogers Avatar answered Oct 12 '22 23:10

Brian Rogers