ietf-wg-jsonpath / draft-ietf-jsonpath-base

Development of a JSONPath internet draft
https://ietf-wg-jsonpath.github.io/draft-ietf-jsonpath-base/
Other
58 stars 20 forks source link

changed type system to just three types #402

Closed gregsdennis closed 1 year ago

gregsdennis commented 1 year ago

Resolves #387 (specifcally implements https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/issues/387#issuecomment-1436079071)

This is effectively an extension of #396 (though not a continuation of that git branch). I purposely didn't use PropositionType because earlier in the text (under #filter-selector), it says

A boolean expression, usually involving the current node, is evaluated and the current node is selected if and only if the expression yields true.

Since the "boolean" language is here, I just stuck with it.

cabo commented 1 year ago

Thanks. As a general observation, I prefer to use a different name for JSONPath-level logicals from JSON-level Booleans. In my draft, I have settled for "logical" for this.

Have you checked what functionality is lost with this approach?

gregsdennis commented 1 year ago

Thanks. As a general observation, I prefer to use a different name for JSONPath-level logicals from JSON-level Booleans. In my draft, I have settled for "logical" for this.

I'm not hung up on naming things. I'm happy with "logical."

Have you checked what functionality is lost with this approach?

Because match() and search() MUST be used as "logicals," they can't be used as comparables. While you get $[?match(@.a, 'a.*')] and $[?!match(@.a, 'a.*')], you don't get $[?match(@.a, 'a.*') == @.b]. But I think that has a very niche use case that I'm sure could be covered with a custom function.

cabo commented 1 year ago

I agree that $[?match(@.a, 'a.*') == @.b] can be expressed without Booleanness: $[?(match(@.a, 'a.*') && true == @.b) || (!match(@.a, 'a.*') && false == @.b)]

cabo commented 1 year ago

I wrote the 44 lines of code needed to check well-typedness in this type system (with the four predefined functions) and things do look good to me. More testing and word-smithing required...

Sample (error messages precede the example; the right column is a JSON representation of the query):

$[?match(@.a, "x*")]                                                           ["$", ["filt", ["func", "match", ["@", "a"], "x*"]]]
*** Cannot use ["func", "match", ["@", "a"], "a.*"] with declared_type logical for required type value in comparable
$[?match(@.a, 'a.*') == @.b]                                                   ["$", ["filt", ["==", ["func", "match", ["@", "a"], "a.*"], ["@", "b"]]]]
*** Cannot use ["func", "match", ["@", "a"], "a.*"] with declared_type logical for required type value in comparable
$[?match(@.a, 'a.*') == @[1]]                                                  ["$", ["filt", ["==", ["func", "match", ["@", "a"], "a.*"], ["@", 1]]]]
$[?(match(@.a, 'a.*') && true == @.b) || (!match(@.a, 'a.*') && false == @.b)] ["$", ["filt", ["or", ["and", ["func", "match", ["@", "a"], "a.*"], ["==", true, ["@", "b"]]], ["and", ["not", ["func", "match", ["@", "a"], "a.*"]], ["==", false, ["@", "b"]]]]]]
*** Cannot use ["func", "length", ["@", "a"]] with declared_type value for required type logical in test expression
$[?length(@.a)]                                                                ["$", ["filt", ["func", "length", ["@", "a"]]]]
$[?length(@.a)==5]                                                             ["$", ["filt", ["==", ["func", "length", ["@", "a"]], 5]]]
$[?count(@.a)==5]                                                              ["$", ["filt", ["==", ["func", "count", ["@", "a"]], 5]]]
*** Cannot use 4 with declared_type value for required type nodes in ["func", "count", 4]
$[?count(4)==5]                                                                ["$", ["filt", ["==", ["func", "count", 4], 5]]]
$[?(@['key']==count(@))]                                                       ["$", ["filt", ["==", ["@", "key"], ["func", "count", ["@"]]]]]
gregsdennis commented 1 year ago

@cabo although we haven't defined one in the spec, a nodelist-function is the outlier. Assuming the follow definition for @glyn's ad-hoc proposal for distinct(), could you run the same tests, please?

Name: distinct

Arguments:

Returns NodelistType - A copy of the argument nodelist with duplicates removed. A duplicate (for the purpose of this comment) is the same JSON value regardless of its location in the data.

Path to test Validity Node selection Note
$[?distinct(@.*)] valid selects the node if it has children This example is not really useful as it should evaluate the same as $[?@.*] because removing duplicates has no effect on empty-ness
$[?distinct(@.*)==42] valid selects the node if the resulting nodelist is just a collection of one or more 42s Again, maybe not too useful.*

I'm not looking for evaluation here, just parsing, as you have above.

* As a note for the second example, the function would return a nodelist here, which is convertible to a value. If @.* results in a nodelist with non-zero nodes all containing the same value, that nodelist would be de-duplicated returning a nodelist with a single node. The convertion to a value would then be the value of that node. If that value is 42, then that node is selected.

I'm not trying to make a case for this function. It's merely an example for your parser.

cabo commented 1 year ago

could you run the same tests, please

$ cat test-data/distinct.jpl
$[?distinct(@.*)]
$[?distinct(@.*)==42]
$ jpt -l -fdistinct-nn test-data/distinct.jpl
$[?distinct(@.*)]     ["$", ["filt", ["func", "distinct", ["@", ["wild"]]]]]
$[?distinct(@.*)==42] ["$", ["filt", ["==", ["func", "distinct", ["@", ["wild"]]], 42]]]
$

So the well-typedness comes out well with these examples for a distinct(nodes) ➔ nodes (distinct-nn). Distinct of course can generate general nodelists, not just singular ones, so this is a bit of the violation of the signature of ==, but then we don't try to distinguish 0-or-1 nodelists from general nodelists in the simple type system we seem to be converging on.

(Get the JPT tool via gem install jpt.)

gregsdennis commented 1 year ago

Distinct of course can generate general nodelists, not just singular ones, so this is a bit of the violation of the signature of == - @cabo

First, thanks for runnig that test and sharing the results.

The type conversion from NodelistType to ValueType (which is accepted by ==) now includes multiple nodelists as being converted to Nothing. The need for this was explained in the final section of https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/issues/387#issuecomment-1436079071.

Since the ABNF only allows Singluar Paths and explicit JSON values to be used with ==, functions become the only way to have a multiple nodelist in a comparison, so I think this conversion is suitable.

cabo commented 1 year ago

Superseded by #403, which cherry-picked a lot from this one. Thank you!

glyn commented 1 year ago

Yes, thanks @gregsdennis.