繁体   English   中英

如何使用表示查询的递归结构解析JSON

[英]How to parse JSON with a recursive structure representing a query

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

{
    "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 ),但我不知道如何转换此字段。

好吧,就像大多数事情一样,有两种方法可以解决这个问题。 我将首先向您展示“快速又肮脏”的方式,然后向您展示我认为是更好的选择。

完成

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

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类中的相应属性进行MessyQueryExpression (而将其他两个不匹配的属性保留为空)。 由于ANDOR属性是同一类的列表,因此它将自动处理递归。

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

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

    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 "";
    }

它有效,但是......。

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


更好的方法

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

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等),则无需进行任何更改-它会照常工作。

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

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

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

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]属性对其进行标记,如下所示:

[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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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