我有一个指定的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
),但我不知道如何转换此字段。
答案 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
类中的相应属性进行匹配(而其他两个不匹配的属性保留为空)。它凭借AND
和OR
属性是同一类的列表来自动处理递归。
当然,这种方法的明显问题是,一旦将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()
方法都很简单明了。而且,如果您想支持其他二进制比较运算符(例如GT
,LT
,NE
等),则无需进行任何更改-它照原样工作。
因此,只需更改我们的根类以使用这个新的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());