issue/bug 将 SelectTokens 与 JsonPath 结合使用

issue/bug using SelectTokens with JsonPath

我在使用具有特定 JsonPath 的 NewtonSoft v8.0.3 JToken.SelectTokens() 时遇到问题。要在 C# 中重现问题:

        string json = "[{\"name\":\"string\",\"value\":\"aString\"},{\"name\":\"number\",\"value\":123},{\"name\":\"array\",\"value\":[1,2,3,4]},{\"name\":\"object\",\"value\":{\"1\":1}}]";
        string path = "$.[?(@.value!=null)]";
        string newJson = String.Empty;

        JToken jt = JToken.Parse(json);
        IEnumerable<JToken> tokens = jt.SelectTokens(path, false);
        newJson = JsonConvert.SerializeObject(tokens, Formatting.Indented);
        // Output is missing objects and arrays??
        // newJson = [{"name": "string","value": "aString"},{"name": "number","value": 123}]

运行 上面的代码不会 return 任何对象或数组?! Json.Net 的 JToken.SelectTokens() 中是否存在错误?

为了验证我使用的路径 http://goessner.net/articles/JsonPath/ which works fine, see this working fiddle

我希望有人能对此有所启发。

更新: 认为这个问题是在我们尝试使用 "null" 值时发生的,但无论我 运行 是什么不等式查询,都没有 {} / [] 是 return 从 JSON 编辑的。

我可以通过使用存在运算符而不是不等运算符使SelectTokens() return得到你想要的结果:

string path = "$.[?(@.value)]"; 

现在所有 4 个对象都已 returned。示例 fiddle.

至于这是否是一个错误,JSONPath article是模棱两可的。它说:

[]   ?()     applies a filter (script) expression.
n/a   ()     script expression, using the underlying script engine. 

但是使用底层脚本引擎究竟意味着什么?如果我在 https://jsonpath.curiousconcept.com/ 测试你的 JSON 和查询表达式,那么 Flow Communications JSONPath 0.3.1Stefan Goessner JSONPath 0.8.3 都会产生一个错误。如果我使用我的存在查询,那么前者可以正常工作,但后者仍然会抛出异常。

Json.NET 应该像现在这样吗?当前的实现隐含地提供了过滤具有原始值的属性的能力。提议的行为将 失去 此功能。即,任何对象或数组值都将始终匹配任何不等式运算符,例如 "$.[?(@.value!='aString')]",这可能不是预期的。

更新

您明确的要求是 运行 查询 "$.[?(@.value!=null)]" 到 return 所有具有 属性 名为 "value" 且存在且具有非空值的对象值,无论该值是原始容器还是嵌套容器。

一方面,这似乎是一个应该可行的合理查询。另一方面,当前能够 运行 对原始值进行不等式查询而不获取所有容器值的能力似乎很有用,也应该是可能的。并且对该特定查询字符串的任何一种解释似乎都是合理的; JSONPath 语言定义过于模糊,留给实施者的解释太多。

获取所需内容的选项包括:

  1. 你当然可以report an issue。 Newtonsoft 可以确定当前行为是错误的,并进行更改。

  2. 您可以 fork 自己的 Json.NET 版本并修改 BooleanQueryExpression.IsMatch(JToken t) 如下:

                case QueryOperator.NotEquals:
                    if (v != null && !EqualsWithStringCoercion(v, Value))
                    {
                        return true;
                    }
                    else if (v == null && r != null)
                    {
                        return true;
                    }
                    break;
    
  3. 您可以使用以下解决方法:

    var path = "$.[?(@.value)]";
    var tokens = jt.SelectTokens(path, false)
        .Where(t => !t["value"].IsNull());
    

    使用扩展方法:

    public static class JsonExtensions
    {
        public static bool IsNull(this JToken token)
        {
            return token == null || token.Type == JTokenType.Null;
        }
    }
    

最后,你问我不明白的是为什么它在javascript 中工作正常,但在Json.Net. 的selectTokens 中却不行。 Json.NET 与 http://jsonpath.com/ 行为不同的原因是:

  1. 关于 script expression

  2. 的标准完全含糊不清
  3. 它们是使用不同的技术和代码库实现的。 Json.NET 将 (@.value!=null) 解释为

    a value of type JValue named "value" that is not equal to the null json primitive.

    而另一个解析器将查询解释为

    a value named "value" that is not equal to the null json primitive.

    与 javascript 相比,c# 是强类型的这一事实可以解释差异。

终于弄清楚是什么导致 .SelectTokens 在使用 JSONPath 时忽略数组和对象。问题发生在 QueryExpressions.cs line 78

JValue v = r as JValue;

这将导致任何 JObject/JArray 的 JToken 变为 null 并因此在比较时被忽略。我解决这个问题的方法是用以下处理 JObject 和 JArray 的代码替换第 78 行:

            JValue v = null;
            switch (r.Type)
            {
                case JTokenType.Object:
                case JTokenType.Array:
                    v = new JValue(r.ToString());
                    break;
                default:
                    v = r as JValue;
                    break;
            }

这将确保在调用 .SelectTokens 时返回 JObject 和 JArray。请随时评论解决方案或建议替代和更好的方法。

更新:很高兴地通知新版本已解决此问题。有关详细信息,请参阅 GITHUB

没有 Jsonpath 的标准实现,因此您的结果会因您使用的解析器而异。但是,您永远不需要检查空值,因为它对 Jsonpath 没有任何意义。

这就是你需要的:

var result = jt.SelectTokens("$.[?(@.value)]");