在C#中使用带有JsonPath的过滤器

时间:2013-08-01 15:42:32

标签: c# json.net jsonpath

我正在使用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" ]
        } ]
    }
}"

2 个答案:

答案 0 :(得分:3)

在查询(?(...)部分)中使用脚本表达式时,需要提供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);
    }
}

答案 1 :(得分: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也会处理路径解析!