3

I am trying to implement what seems like a simple JSON path filter, but failing to get it working. Wondering if others more experience with Json.NET's implementation of JSON path have ideas on next steps.

This scenario fails but I think should work?

var jsonText = @"{
    'event': {
        'data': {
            'intField': 1,
            'stringField': 'hello'
        }
    }
}";

JObject json = JsonConvert.DeserializeObject<JObject>(jsonText);
string jsonPath = "$.event.data[?(@.intField == 1)]";
IList<JToken> output = json.SelectTokens(jsonPath).ToList();

// this check fails
Assert.IsTrue(output.ToList().Count > 0);

If I massage the JSON payload by adding a dummy array around the 'data' object, then I can get the query working. However, I would rather not massage the JSON payload.

var jsonText = @"{
    'event': {
        'data': [{
            'intField': 1,
            'stringField': 'hello'
        }]
    }
}";

JObject json = JsonConvert.DeserializeObject<JObject>(jsonText);
string jsonPath = "$.event.data[?(@.intField == 1)]";
IList<JToken> output = json.SelectTokens(jsonPath).ToList();

// now this works
Assert.IsTrue(output.ToList().Count > 0);
2

2 Answers 2

6

The problem is that Json.NET's implementation of the JsonPATH filter expression operator [?()] only works for filtering of objects inside collections (arrays) -- not objects inside other objects. This limitation is reported in Issue #1256: JSONPath scripts not executing correctly for objects, to which Newtonsoft replied,

I'm not sure about this. Nothing in the JSONPath says that filters should apply to objects.
JamesNK commented on Mar 24, 2017

This limitation comes up here from time to time, e.g. What's the correct JsonPath expression to search a JSON root object using Newtonsoft.Json.NET? or How to filter a non-array in JsonPath. You might want to add a comment to the issue on GitHub if you would find filtering for objects inside objects useful.

A workaround that sometimes does the job is to add the recursive descent operator .. between the containing object property name and the filter expression operator, like so:

string jsonPath = "$.event.data..[?(@.intField == 1)]";

And, in your specific case, the modified query now works and selects one object. Demo fiddle here.

Of course, this modified query will also match something like:

{
  "event": {
    "data": {
      "extra_added_level_of_nesting": {
        "intField": 1,
        "stringField": "hello"
      }
    }
  }
}

(fiddle #2 here) so the workaround may not be sufficient for your needs.

The workaround succeeds because, apparently, Json.NET considers the results returned by the recursive descent operator to be a collection, and thus can apply a filter expression operator to it.

Sign up to request clarification or add additional context in comments.

Comments

0

Note that given the following JSON: {"x":{"a":1, "b":2}} and the given JSONPath expression: $.x.* - the result is [1, 2] - not [{"a":1}, {"b":2}] as some may expect. That's how JSONPath works. A match which is a map member resolves to the member's value.

Similarly, for the JSONPath expression, $.x[?@] the result is [1, 2] - just the values of members of x, and not [{"a":1}, {"b":2}]. Therefore you are not able to access @.a.

Similarly, for the JSONPath expression $[?@] - the result is [{"a":1, "b":2}] - just the value of x, and not ["x":{"a":1, "b":2}]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.