如何使用表示查询的递归结构解析JSON [英] How to parse JSON with a recursive structure representing a query

查看:46
本文介绍了如何使用表示查询的递归结构解析JSON的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个指定的JSON文档,如下所示:

I have a specified JSON document, like this:

{
    "user":"human1",
    "subsystems":[1,2,3],
    "query":{"AND":[
                        {"eq":["key1","val1"]},
                        {"eq":["key2","val2"]},
                        {"OR":[
                            {"eq":["subkey1","subval1"]},
                            {"eq":["subkey2","subval2"]}]}
        ]
    }
}

query字段的预期转换:

(key1 eq val1 and key2 eq val2 and (subkey1 eq subval1 OR subkey2 eq subval2))

我正在使用Newtonsoft.Json(JsonConvert.DeserializeObject),但我不知道如何转换此字段.

I am using Newtonsoft.Json (JsonConvert.DeserializeObject), and I don't understand how to transform this field.

推荐答案

与大多数事物一样,有两种方法可以解决此问题.首先,我将向您展示快速又肮脏"的方式,然后向您展示我认为是更好的选择.

Well, like most things, there are a couple of ways to handle this. I'll first show you the "quick and dirty" way, and then show you what I think is a better alternative.

如果您真的不太在乎代码的样子,而只是想尽快获得最终结果,则可以使用以下类将其反序列化为:

If you really don't care too much about what the code looks like, and you just want to arrive at the final result as quickly as possible, you can use the following classes to deserialize into:

class RootObject
{
    public string User { get; set; }
    public List<int> Subsystems { get; set; }
    public MessyQueryExpression Query { get; set; }
}

class MessyQueryExpression
{
    public List<string> EQ { get; set; }
    public List<MessyQueryExpression> AND { get; set; }
    public List<MessyQueryExpression> OR { get; set; }
}

然后像这样反序列化:

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

之所以可行,是因为Json.Net能够将查询操作符名称与MessyQueryExpression类中的相应属性进行匹配(而将其他两个不匹配的属性保留为空).通过ANDOR属性是同一类的列表,它可以自动处理递归.

This works because Json.Net is able match the query operator name with the corresponding property in the MessyQueryExpression class (while leaving the other two non-matching properties null). It handles the recursion automatically by virtue of the AND and OR properties being lists of the same class.

当然,这种方法的明显问题是,一旦将JSON反序列化,每个MessyQueryExpression的真正含义还不清楚.您必须环顾四周",直到找到具有数据的集合,然后您才知道操作员是什么. (并且,如果您将来希望增加对更多运算符的支持,那么您将需要为每个类添加另一个列表属性,这会使事情更加混乱.)

Of course, the obvious problem with this approach is, once the JSON is deserialized, it's not very clear what each MessyQueryExpression really represents. You have to "look under rocks" until you find the collection that has the data, and only then do you know what the operator is. (And if you wanted to add support for more operators in the future, then you would need to add another list property to the class for each one, which muddies things up even more.)

为了说明我的意思,这是您需要实现ToString()方法以将查询表达式树转换为可读字符串的方式:

To show what I mean, here is how you would need to implement the ToString() method to turn the query expression tree into a readable string:

    public override string ToString()
    {
        if (EQ != null && EQ.Count > 0) return string.Join(" eq ", EQ);
        if (AND != null && AND.Count > 0) return "(" + string.Join(" AND ", AND) + ")";
        if (OR != null && OR.Count > 0) return "(" + string.Join(" OR ", OR) + ")";
        return "";
    }

它可以工作,但是...好极了.

It works, but... yuck.

提琴: https://dotnetfiddle.net/re019O

处理JSON中的递归查询表达式的更明智的方法是使用复合这样的类结构:

A more sensible way to handle the recursive query expression in the JSON is to use a composite class structure like this:

abstract class QueryExpression
{
    public string Operator { get; set; }
}

class CompositeExpression: QueryExpression  // AND, OR
{
    public List<QueryExpression> SubExpressions { get; set; }

    public override string ToString()
    {
        return "(" + string.Join(" " + Operator + " ", SubExpressions) + ")";
    }
}

class BinaryExpression: QueryExpression  // EQ
{
    public string Value1 { get; set; }
    public string Value2 { get; set; }

    public override string ToString()
    {
        return Value1 + " " + Operator + " " + Value2;
    }
}

现在,我们有了一个清晰的Operator属性来保存操作员名称.每个种类表达式都有其自己的子类,这些子类具有适当的属性来保存数据.了解正在发生的事情要容易得多.您可以看到每个类上的ToString()方法都非常简单明了.而且,如果您想支持其他二进制比较运算符(例如GTLTNE等),则无需进行任何更改-它会照常工作.

Now we have a clear-cut Operator property to hold the operator name. Each kind of expression has its own subclass with appropriate properties to hold the data. It's much easier to understand what is going on. You can see that the ToString() methods on each class are simple and straightforward. And, if you want to support other binary comparison operators (e.g. GT, LT, NE, etc.), you don't need to change anything -- it will work as is.

因此,只需更改我们的根类以使用此新的QueryExpression类而不是MessyQueryExpression,我们就可以开始了,对吧?

So, just change our root class to use this new QueryExpression class instead of the MessyQueryExpression and we're ready to go, right?

class RootObject
{
    public string User { get; set; }
    public List<int> Subsystems { get; set; }
    public QueryExpression Query { get; set; }
}

不太快.由于类结构不再与JSON匹配,因此Json.Net不会知道如何以所需的方式填充类.为了弥合差距,我们需要定制一个 JsonConverter .转换器通过将JSON加载到中间的 JObject 和然后查看操作符名称,以确定要实例化和填充的子类.这是代码:

Not so fast. Since the class structure no longer matches the JSON, Json.Net isn't going to know how to populate the classes the way we want. To bridge the gap, we need to make a custom JsonConverter. The converter works by loading the JSON into an intermediate JObject and then looking at the operator name to determine which subclass to instantiate and populate. Here is the code:

class QueryExpressionConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(QueryExpression).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JProperty prop = JObject.Load(reader).Properties().First();
        var op = prop.Name;
        if (op == "AND" || op == "OR")
        {
            var subExpressions = prop.Value.ToObject<List<QueryExpression>>();
            return new CompositeExpression { Operator = op, SubExpressions = subExpressions };
        }
        else
        {
            var values = prop.Value.ToObject<string[]>();
            if (values.Length != 2)
                throw new JsonException("Binary expression requires two values. Got " + values.Length + " instead: " + string.Join(",", values));
            return new BinaryExpression { Operator = op, Value1 = values[0], Value2 = values[1] };
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        QueryExpression expr = (QueryExpression)value;
        JToken array;
        if (expr is CompositeExpression)
        {
            var composite = (CompositeExpression)expr;
            array = JArray.FromObject(composite.SubExpressions);
        }
        else
        {
            var bin = (BinaryExpression)expr;
            array = JArray.FromObject(new string[] { bin.Value1, bin.Value2 });
        }
        JObject jo = new JObject(new JProperty(expr.Operator, array));
        jo.WriteTo(writer);
    }
}

要将转换器类与QueryExpression类绑定,我们需要使用[JsonConverter]属性对其进行标记,如下所示:

To tie the converter class to the QueryExpression class, we need to mark it with a [JsonConverter] attribute like this:

[JsonConverter(typeof(QueryExpressionConverter))]
abstract class QueryExpression
{
    public string Operator { get; set; }
}

现在一切正常:

var root = JsonConvert.DeserializeObject<RootObject>(json);
Console.WriteLine(root.Query.ToString());

提琴: https://dotnetfiddle.net/RdBnAG

这篇关于如何使用表示查询的递归结构解析JSON的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆