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

时间:2018-07-24 09:38:43

标签: c# json parsing recursion json.net

我有一个指定的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),但我不知道如何转换此字段。

1 个答案:

答案 0 :(得分:0)

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

完成任务

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

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属性是同一类的列表来自动处理递归。

当然,这种方法的明显问题是,一旦将JSON反序列化,每个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中处理递归查询表达式的一种更明智的方法是使用composite类结构,如下所示:

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