简体   繁体   中英

Deserializing JSON with an unknown number of nested arrays representing a query

I have some JSON as shown below.

{
  "queryEntry": [
    [
      {
        "type": "expression",
        "key": "Account.Name",
        "operators": "=",
        "value": "asd"
      }
    ],
    {
      "type": "conjunction",
      "value": "OR",
      "key": null,
      "operators": null
    },
    [
      {
        "type": "expression",
        "key": "Account.TotalEmployees",
        "operators": "=",
        "value": "123"
      },
      {
        "type": "conjunction",
        "value": "AND",
        "key": null,
        "operators": null
      },
      [
        {
          "type": "expression",
          "key": "Account.LastYearRevenue",
          "operators": "=",
          "value": "123"
        },
        {
          "type": "conjunction",
          "value": "OR",
          "key": null,
          "operators": null
        },
        [
          {
            "type": "expression",
            "key": "Account.Last5YearRevenue",
            "operators": "=",
            "value": "123"
          }
        ]
      ]
    ],
    {
      "type": "conjunction",
      "value": "AND",
      "key": null,
      "operators": null
    },
    [
      {
        "type": "expression",
        "key": "Account.OwnerName",
        "operators": "=",
        "value": "asd"
      }
    ]
  ]
}

This is actually a where condition in SQL which we are generating from jQuery. I have to deserialize this string to a class in C#.

Could anyone guide me as to what class I should create in order to deserialize this? Also, it is unknown how deep the nested array goes.

This is a difficult JSON format to work with, because it is a recursive array structure where the arrays can contain either objects or arrays or a combination of both. I can see that the JSON objects represent pieces of a query, but the query subexpressions are not within the objects like I would expect. Rather, it seems like the arrays are being used as literal parenthesis for grouping the query!

I think the best way to get this to deserialize into a halfway sane class structure is to use the Composite pattern , where one class represents both a single piece of the query (ie expression or conjunction) and also a grouping of the same. Something like this:

class QueryEntry
{
    public string Type { get; set; }
    public string Key { get; set; }
    public string Operators { get; set; }
    public string Value { get; set; }
    public List<QueryEntry> Group { get; private set; }
    public bool IsGroup { get { return Group.Count > 0; } }

    public QueryEntry()
    {
        Group = new List<QueryEntry>();
    }
}

We also need a wrapper class to handle the queryEntry property at the root of the JSON:

class RootObject
{
    public QueryEntry QueryEntry { get; set; }
}

Now, since the JSON could be an object or a heterogeneous array at any level, we can't just throw this class at Json.Net and expect it to deserialize properly out of the gate. (You are using Json.Net aren't you? Your question did not say.) We will need to use a custom JsonConverter to translate the JSON structure into the composite class structure:

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        QueryEntry entry = new QueryEntry();
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Object)
        {
            serializer.Populate(token.CreateReader(), entry);
        }
        else if (token.Type == JTokenType.Array)
        {
            foreach (JToken child in token)
            {
                entry.Group.Add(child.ToObject<QueryEntry>(serializer));
            }
        }
        return entry;
    }

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

To deserialize, we can pass an instance of the converter to DeserializeObject<T> like this:

var root = JsonConvert.DeserializeObject<RootObject>(json, new QueryEntryConverter());

Finally, if we want to convert the deserialized query expression tree into a readable string, we will need a recursive ToString() method on the QueryEntry class:

    public override string ToString()
    {
        if (IsGroup)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append('(');
            foreach (QueryEntry entry in Group)
            {
                sb.Append(entry.ToString());
            }
            sb.Append(')');
            return sb.ToString();
        }
        else if (Type == "expression")
        {
            return Key + ' ' + Operators + ' ' + Value;
        }
        else if (Type == "conjunction")
        {
            return ' ' + Value + ' ';
        }
        return string.Empty;
    }

Then we can do:

Console.WriteLine(root.QueryEntry.ToString());

With the JSON in your question, the output would be:

((Account.Name = asd) OR (Account.TotalEmployees = 123 AND (Account.LastYearRevenue = 123 OR (Account.Last5YearRevenue = 123))) AND (Account.OwnerName = asd))

Here is a working demo: https://dotnetfiddle.net/D3eu5J

you can covert using JsonConvert as follows

MyData tmp = JsonConvert.DeserializeObject<MyData>(json);
foreach (string typeStr in tmp.type[0])
{
    // Do something with typeStr
}

may be create the classs in the properties as fields if you want you use [jsonproperty] annotation for the class properties

class MyData
{
  @JsonProperty("t")

public string t; public bool a;

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM