在 C# 中通过动态过滤器查询过滤复杂 JSON
Filtering Complex JSON by dynamic filter query in c#
我正在尝试在我的一个项目中创建一个 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 过滤器返回所有元素和内部 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();
结果将符合您所说的预期。
仅供参考,$.. 选择父元素中的所有元素,无论多深。
--- 编辑:
关于反向操作的后续问题。这是可能的,但你必须以不同的方式处理它。由于这些项目位于源对象的不同级别,我认为最好构造一个包含您想要的结果数组的新 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)])]
意味着 selecting 令牌 来自父数组 使用应用于 InnerArray
属性 子对象的过滤器。
所以在英文中,意思是:
给定一个父对象,如果它的 InnerArray
包含任何定义了 id
属性 的对象和这样的值 id
属性等于301
,那么return父对象.
对于来自 InnerArray
的 select 个令牌,你应该这样做(假设 id
是唯一的):
$[?(@.InnerArray[?(@.id==301)])].InnerArray[0]
结果是:
[
{
"id":301,
"innerField1":"f1",
"innerField2":"f2"
}
]
然后你需要替换 selected parent:
中的 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 结构的通用过滤,我想我无法回答。您需要合并 selected 令牌才能获得您的期望,我不知道如何轻松定义这些合并操作。
我认为可以创建一个模型。见下方设计。
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.DeserializeObject(...) 方法反序列化字符串,然后你可以使用 Linq 过滤掉你的对象。
var deserializeList = JsonConvert.DeserializeObject<List<Outer>>(serializedObject);
var outer = deserializeList.FirstOrDefault(x => x.id == 1);
Console.WriteLine(outer?.id);
希望对您有所帮助。
我正在尝试在我的一个项目中创建一个 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 过滤器返回所有元素和内部 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();
结果将符合您所说的预期。
仅供参考,$.. 选择父元素中的所有元素,无论多深。
--- 编辑:
关于反向操作的后续问题。这是可能的,但你必须以不同的方式处理它。由于这些项目位于源对象的不同级别,我认为最好构造一个包含您想要的结果数组的新 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)])]
意味着 selecting 令牌 来自父数组 使用应用于 InnerArray
属性 子对象的过滤器。
所以在英文中,意思是:
给定一个父对象,如果它的 InnerArray
包含任何定义了 id
属性 的对象和这样的值 id
属性等于301
,那么return父对象.
对于来自 InnerArray
的 select 个令牌,你应该这样做(假设 id
是唯一的):
$[?(@.InnerArray[?(@.id==301)])].InnerArray[0]
结果是:
[
{
"id":301,
"innerField1":"f1",
"innerField2":"f2"
}
]
然后你需要替换 selected parent:
中的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 结构的通用过滤,我想我无法回答。您需要合并 selected 令牌才能获得您的期望,我不知道如何轻松定义这些合并操作。
我认为可以创建一个模型。见下方设计。
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.DeserializeObject(...) 方法反序列化字符串,然后你可以使用 Linq 过滤掉你的对象。
var deserializeList = JsonConvert.DeserializeObject<List<Outer>>(serializedObject);
var outer = deserializeList.FirstOrDefault(x => x.id == 1);
Console.WriteLine(outer?.id);
希望对您有所帮助。