通过c#中的动态过滤查询过滤复杂的JSON



我正试图在我的一个项目中创建一个JSON过滤,这里的JSON是动态的,所以不能创建模型,过滤器将来自用户,我的示例JSON是

[
{
"id": 101,
"field1": "f1",
"field2": "f2",
"InnerArray": [
{
"id": 201,
"innerField1": "f1",
"innerField2": "f2"
},
{
"id": 202,
"innerField1": "f1",
"innerField2": "f2"
}
]
},
{
"id": 102,
"field1": "ff1",
"field2": "ff2",
"InnerArray": [
{
"id": 301,
"innerField1": "f1",
"innerField2": "f2"
},
{
"id": 302,
"innerField1": "f1",
"innerField2": "f2"
}
]
}
]

我正在尝试通过SelectToken((过滤它,除了内部数组外,它会很好地工作例如,如果查询是

string filter = "$.[?(@.id==101)]";
JToken filteredData = data.SelectToken($"{filter}");
//We will get
{
"id": 101,
"field1": "f1",
"field2": "f2",
"InnerArray": [
{
"id": 201,
"innerField1": "f1",
"innerField2": "f2"
},
{
"id": 202,
"innerField1": "f1",
"innerField2": "f2"
}
]
}

但是如果我想通过内部数组元素过滤JSOn,那么它将无法工作

string filter = "$.[?(@.InnerArray[?(@.id==301)])]";
JToken filteredData = data.SelectToken($"{filter}");
//Result is 
{
"id": 102,
"field1": "ff1",
"field2": "ff2",
"InnerArray": [
{
"id": 301,
"innerField1": "f1",
"innerField2": "f2"
},
{
"id": 302,
"innerField1": "f1",
"innerField2": "f2"
}
]
}

我的期望是

{
"id": 102,
"field1": "ff1",
"field2": "ff2",
"InnerArray": [
{
"id": 301,
"innerField1": "f1",
"innerField2": "f2"
}
]
}

InnerArray Filter返回所有元素,内部JSON PATH不接受,有没有其他方法可以定义JSON路径?或者有任何替代方案可以动态过滤JSON,因为这里JSON将是动态的,过滤器将是动态

有可能,我已经构建了以下可执行代码来做到这一点:

为了使其可解析,否则JToken.Parse表示json不能作为数组启动。

string sourceFile = File.ReadAllText("./source.json");
JToken source = JToken.Parse(sourceFile);
List<JToken> tokensToRemove = source.SelectTokens("$..*[?(@.id == 101 || @.id == 301)]").ToList();
tokensToRemove.ForEach(t => t.Remove());
string result = source.ToString();

结果将符合您所说的预期。

FYI,$。。从父元素中选择所有元素,无论深度如何。

---编辑:

关于反其道而行之的后续问题。这是可能的,但你必须采取不同的做法。由于这些项位于源对象的不同级别,所以我认为最好构建一个新的JObject,其中包含一个想要的结果数组。

像这样:

string sourceFile = File.ReadAllText("./source.json");
JToken source = JToken.Parse(sourceFile);
List<JToken> tokensToKeep = source.SelectTokens("$..*[?(@.id == 101 || @.id == 301)]").ToList();
JObject resultObject = new JObject();
JArray array = new JArray();
resultObject.Add("array", array);
tokensToKeep.ForEach(t => array.Add(t));
string result = resultObject.ToString();

我认为JSONPath无法实现您的期望(如果我错了,请纠正我(。

$[?(@.InnerArray[?(@.id==301)])]

是指使用应用于子对象的InnerArray属性的过滤器从父数组中选择标记

所以在英语中,它的意思是:

给定父对象,如果其InnerArray包含任何定义id属性的对象,并且该id属性的值等于301,则返回父对象

要从InnerArray中选择令牌,您应该执行(假设id是唯一的(:

$[?(@.InnerArray[?(@.id==301)])].InnerArray[0]

结果是:

[
{
"id":301,
"innerField1":"f1",
"innerField2":"f2"
}
]

然后,您需要替换所选父级中的InnerArray

var selectedInnerArray = obj.SelectToken(
"$[?(@.InnerArray[?(@.id==301)])].InnerArray[0]");
var selectedParent = obj.SelectToken("$[?(@.InnerArray[?(@.id==301)])]");
var result = selectedParent.DeepClone();
result["InnerArray"].Replace(new JArray(selectedInnerArray));

result看起来像您的期望。

至于使用动态过滤器和JSON结构的通用过滤,我想我无法回答这个问题。您需要合并选定的令牌才能得到您的期望,我不知道如何轻松定义这些合并操作。

我认为创建一个模型是可能的。参见以下设计。

internal class Inner
{
public int id { get; set; }
public string innerField1 { get; set; }
public string innerField2 { get; set; }
}
internal class Outer
{
public int id { get; set; }
public string field1 { get; set; }
public string field2 { get; set; }
public List<Inner> InnerArray { get; set; }
}

下的该模型示例

private static void Main(string[] args)
{
List<Outer> list = new List<Outer>
{
new Outer() 
{ 
id = 1, field1 = "f1", 
field2 = "f2", 
InnerArray = new List<Inner>() 
{ 
new Inner() 
{ 
id = 1,
innerField1="if1",
innerField2="if2" 
} 
} 
},
new Outer() 
{ 
id = 2, 
field1 = "f1", 
field2 = "f2", 
InnerArray = new List<Inner>() 
{ 
new Inner() 
{ 
id = 1,
innerField1="if1",
innerField2="if2" 
} 
} 
}
};
string serializedObject = JsonConvert.SerializeObject(list, Formatting.Indented);
Console.WriteLine(serializedObject);
Console.ReadLine();
}

在serializedObject中,您将获得json字符串,同样,您可以使用JsonConvert.DescializeObject(…(方法对字符串进行反序列化,然后使用Linq过滤出您的对象。

var deserializeList = JsonConvert.DeserializeObject<List<Outer>>(serializedObject);
var outer = deserializeList.FirstOrDefault(x => x.id == 1);
Console.WriteLine(outer?.id);

希望得到帮助。

最新更新