在 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);

希望对您有所帮助。