【问题标题】:Using filters with JsonPath in C#在 C# 中使用带有 JsonPath 的过滤器
【发布时间】:2013-08-01 15:42:32
【问题描述】:

我正在使用JsonPath for C# 来查询一些 JSON 数据。 JsonPath 没有自带解析器,因此根据Rick Sladkey's advice,我使用Json.NET 将我的Json 字符串解析为嵌套IDictionary 对象、IList 数组和原语的集合。然后我使用JsonPath对其进行过滤(在添加Rick Sladkey's answer中建议的类之后)。

作为参考,这是我的代码中实际处理所有这些的部分:

// string exampleJsonString defined below
string query = "$.store.book[*].title" // we should get a list of all titles

// step 1: use Json.NET to parse the json string into a JObject
JObject parsedJson = JObject.Parse(exampleJsonString); 

// step 2: use JsonPath with a custom ValueSystem (JsonNetValueSystem)
JsonPathContext context = new JsonPathContext    
        { ValueSystem = new JsonNetValueSystem() }; 

// step 3: get results using JsonPath
List<object> toRet = context.SelectNodes(parsedJson,
        query).Select(node => node.Value).ToList();

我首先使用 JsonPath 的原因是它的过滤器功能。你不仅可以像"$.store.book[*].title"这样的普通查询(获取书店中所有书名的数组),还可以像"$.store.book[?(@.category == 'fiction')].title"这样的查询(获取书店中所有类别匹配“小说”的书名的数组')。我需要能够将整个查询作为字符串传递,因此能够在查询时进行过滤非常有帮助。

很遗憾,我在使用此过滤器功能时遇到了一些问题。我希望我必须对 JsonNetValueSystem 类(最初在 the aforementioned stack overflow answer 中定义)或 JsonPath 命名空间(您可以从 JsonPath's google code page 获取 JsonPath.cs)进行调整。如果有一些外部工具或 Json.NET 的替代解析机制可以让我保持 JsonPath 的过滤而不必编写太多额外的代码,那将是理想的,但我很确定我将不得不改变 JsonNetValueSystem或 JsonPath 本身。 (这两个都相当容易更改,因为它们只是 .cs 文件,但如果没有更多工作,我无法真正深入研究 Json.NET。)

我实际上似乎无法弄清楚原始 JsonPath 代码在哪里处理过滤,也无法弄清楚为什么 JsonNetValueSystem 类会剥夺它的功能。任何有关如何在查询字符串中添加过滤功能的建议将不胜感激。即使只是“不要乱用 JsonPath,只需更改 JsonNetValueSystem”,反之亦然。

string exampleJsonString = "{
    "store": {
        "book": [ {
            "category": "reference",
            "author": "Nigel Rees",
            "title": "Sayings of the Century",
            "price": 8.95
        }, {
            "category": "fiction",
            "author": "Evelyn Waugh",
            "title": "Sword of Honour",
            "price": 12.99
        }, {
            "category": "fiction",
            "author": "Herman Melville",
            "title": "Moby Dick",
            "isbn": "0-553-21311-3",
            "price": 8.99
        }, {
            "category": "fiction",
            "author": "J. R. R. Tolkien",
            "title": "The Lord of the Rings",
            "isbn": "0-395-19395-8",
            "price": 22.99
        } ],
        "bicycle": [ {
            "color": "red",
            "price": 19.95,
            "style": [ "city", "hybrid" ]
        }, {
            "color": "blue",
            "price": 59.91,
            "style": [ "downhill", "freeride" ]
        } ]
    }
}"

【问题讨论】:

  • 您是否尝试过将 JSON 解析为类对象并使用 Linq 进行过滤?
  • 问题是,我不会事先知道JSON的结构是什么。例如,我无法设置具有 Category、Author、Title 和 Price 的 Book 类,因为该代码旨在用于各种各样的数据库,其中大多数都没有 Book 对象。所以我基本上每次都将字符串解析为新的 JObject 。我不知道为每个新数据库创建自定义类的方法。有吗?如果是这样,那就太棒了。如果不是,我想我仍然坚持我所拥有的,据我所知。我不太了解 Linq,所以如果有办法,那就太好了。
  • 无法理解。我仍在尝试自己弄清楚 Linq。但似乎 JSON.Net 可能会提供(通过一些工作)您正在寻找的东西。我建议从这里查看文档:james.newtonking.com/projects/json/help/index.html?topic=html/… 可能需要您利用 JArray 和 IObject 方法才能到达您需要的位置。

标签: c# json.net jsonpath


【解决方案1】:

当您在查询中使用脚本表达式(?(...) 部分)时,您需要提供 ScriptEvaluator 方法来评估脚本。不幸的是,它们没有为 C# 版本提供默认实现。您需要提供该实现。

开箱即用,这不是要解决的最微不足道的问题,您需要编写一个解释器。您有两个选择:使用 CodeDOM 将脚本编译为 C# 代码(或任何您喜欢的语言)并执行,使用 Roslyn 解析脚本并进行评估,任何可行的方法。

针对这种特殊情况的快速而肮脏的解决方案是在您的脚本评估器方法中执行类似的操作:

object EvaluateScript(string script, object value, string context)
{
    if (script == "@.category == 'fiction'")
    {
        var obj = value as JObject;
        return (string)obj["category"] == "fiction";
    }
    return null;
}

这是另一个利用 IronPython 评估脚本的解决方案。

public class IronPythonScriptEvaluator
{
    private Lazy<ScriptEngine> engine = new Lazy<ScriptEngine>(() => Python.CreateEngine());
    private ScriptEngine Engine { get { return engine.Value; } }

    private const string ItemName = "_IronPythonScriptEvaluator_item";

    public object EvaluateScript(string script, object value, string context)
    {
        var cleanScript = CleanupScript(script);
        var scope = Engine.CreateScope();
        scope.SetVariable(ItemName, value);
        return Engine.Execute<bool>(cleanScript, scope);
    }

    private string CleanupScript(string script)
    {
        return Regex.Replace(script, @"@", ItemName);
    }
}

【讨论】:

  • 啊哈,这看起来像是我必须做的。感谢您为我指明正确的方向。 :) 完成后我会发布我所拥有的。
  • 如果您可以使用 IronPython 等动态语言,您可以使用它来评估您的脚本。我已经包含了一个你如何使用它的例子。
  • 这个ScriptEvaluator的文档在哪里?
  • @TheOddler:据我所知,没有任何关于 C# 实现的文档(或网站上提供的任何其他 PHP 实现)。您必须查看源代码以弄清楚它需要什么以及它正在尝试做什么。这不是最复杂的一段代码,所以应该很简单。
【解决方案2】:

另一种解决方案是改用Manatee.Json。它有一个内置的原生 JSONPath 实现和解析器(连同模式和序列化程序)。此外,您不需要将路径表示为字符串。 Manatee.Json 有一个流畅的界面,可用于构建路径,包括表达式支持。

要代表$.store.book[*].title,你应该有

var path = JsonPathWith.Name("store")
                       .Name("book")
                       .Array()
                       .Name("title");

对于您的示例$.store.book[?(@.category == 'fiction')].title,您可以使用

var path = JsonPathWith.Name("store")
                       .Name("book")
                       .Array(jv => jv.Name("category") == "fiction")
                       .Name("title");

更重要的是,对这些表达式中的字段的支持也有限。

如果你的路径源是一个字符串,Manatee.Json 也会处理路径解析!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-01
    • 2022-09-29
    • 1970-01-01
    • 2023-03-09
    • 2014-07-01
    相关资源
    最近更新 更多