Code change:
And: allow normal string comparison. use some phrases consistently (editorial change only). (Reviewers may like to view .) The options under consideration in issue were: . . . The NaB proposal, but with left-to-right evaluation and short-circuiting. The option of forcing a total order among all values, like Erlang does. The option of not selecting the current item if any type-mismatched comparisons occur anywhere in the expression. This is option 2 with string comparisons. Fixes URL
(Do you mean beyond-BMP characters?) You do not need to encode beyond-BMP characters, but if you do, you indeed go through UTF-16 surrogate pairs. No, and that is true of other backslash-encoded characters, too. You can sort by Unicode Scalar Values. You can also sort by UTF-32 code units (obviously) and by UTF-8 code units (a.k.a. bytes) — UTF-8 was careful to preserve sorting order. Grüße, Carsten
Merging. We can fine-tune in a follow-on PR.
What is the expected behavior for expressions which are not semantically valid? For example, is only defined for numbers. So what if someone does Is this a parse error? Does it just evaluate to for all potential values? This kind of thing will be important to define if/when we decide to support more complex expressions (e.g. with mathematic operators) or inline JSON values such as objects or arrays.
Here you can see the current behaviour of the various implementations for a similar case: URL
Thanks NAME But for this issue, I'd like to explore what we want for the specification. Also, for some context, I'm updating my library to operate in a "specification compliant" mode by default. Optionally, users will be able to configure for extended support like math operations and inline JSON (as I mentioned above). But until that stuff is actually included in the spec, I'd like it turned off by default.
The spec says: and the grammar in the spec includes as syntactically valid. (We could tighten up the grammar to exclude cases where non-numeric literals appear in ordered comparisons, but I don't think there's a great benefit in doing so.) The spec also says: So, evaluates to "false".
NAME You're quite right. I noticed that the other day and fixed it, but I quoted the previous spec before the fix was merged. Apologies. Please see the revised wording in the filter .
I don't understand numeric only semantic constraint. Is there a reason behind it? JSON has very few value types and lexical ordering of strings is well defined in Unicode. Standards like ISO8601, commonly used for representing date-time values in JSON, give consideration to lexical ordering. So a useful filter for me could be to return JSON objects that have a date greater than (or equal) to a date taken from somewhere else in the same instance. Having the comparison evaluate to false could also be confusing. For , what should yield for ? Should it be true? If so such an expression would return which is a non-numeric result even though a numeric comparison was performed. Or should breaking the semantic rule about numerics cause the entire predicate to yield false, regardless?
I'd like to be sure that we keep to the topic here and not get distracted by my choice of example. Yes, comparisons with strings is widely well-defined, but this issue is about comparisons that don't align with what's in the spec currently. Currently, as defined by the spec, strings are not comparable. Maybe my question could be better served with the boolean example mentioned by NAME What happens in this case?
It's hard to tell from . But consider something that is stated in the draft, "Note that comparisons between structured values, even if the values are equal, produce a "false" comparison result." So, the draft considers comparisons of structured values to be well formed, but always evaluates the comparison to false. Consequently, given element-for-element equal arrays and , and both evaluate to , and so presumably and both evaluate to . This suggests to me that the draft's authors intend comparisons that may be regarded as "invalid" to evaluate to . Would the draft also have gives and gives ? Regardless, the draft's rules for comparing structured values appear to be incompatible with all existing JSONPath implementations, no existing implementation follows these rules, as evidenced in the Comparisons by and . None appear to evaluate both and to . These include both implementations in which the expression is well defined, and implementations in which the expression is a type error. For example, the Javascript implementations evaluate as , and as , because Javascript is comparing references to arrays (rather than values.) Implementations in which the expression is a type error either report an error or return an empty result list. I don't think you'd find precedence for these rules in any comparable query language, whether JSONPath, JMESPath, JSONAta, or XPATH 3.1. There is priori experience for defining comparisons for all JSON values (see e.g. JAVA Jayway), just as there is priori experience for treating some comparisons as type errors and taking those terms out of the evaluation (see e.g. JMESPath.) But the draft's approach to comparisons is different. It does not seem to follow any prior experience.
What the says about comes down to the semantics of the comparison . The Filter Selector says: Since is non-numeric, the comparison produces a "false" result. Therefore, always results in an empty nodelist.
But the spec doesn't say that the result is , and it's incorrect to assume this behavior. It's equally valid to assume that such a comparison is a syntax error. This is the crux of this issue.
The spec describes comparisons as boolean expressions which therefore have the value true or false. The difficulty of using the terms and in that context would be possible confusion with the JSON values and . The spec is currently inconsistent in describing the results of boolean expressions as true/false or "true"/"false", but the fix there is to be consistent. As for for the syntax side, the spec's grammar implies that is syntactically valid.
I feel it would be better to be explicit.
And always returns a non-empty result list. As does . If the idea is that expressions involving unsupported constructions should return an empty result list, than none of the above expressions should return anything. There is prior experience for matching nothing in all cases, e.g. in JMESPath, both and , where the greater-than comparison isn't supported, match nothing. There is also prior experience in the JSONPath Comparisons where the presence of unsupported comparisons always results in an empty list. But the approach taken in the draft appears to be a minority of one. There appears to be no prior experience for it.
I don't think we can be any more explicit than the following productions: What did you have in mind?
I had a look and couldn't find these tests. Please could you provide a link or links.
It depends on what we want. If we say is syntactically valid, then we need to explicitly define that it (and NAME variants, etc) evaluate to (boolean, not JSON) because they are nonsensical (semantically invalid). But if we say that these expressions are syntactically invalid, then we need to update the ABNF to reflect that by splitting and so that we can properly restrict the syntax.
Even if we made syntactically invalid, the same expression could arise from, for example, where is . So, since the spec needs to be clear about the semantics of such expressions, I don't really see the benefit of ruling some of them out syntactically if that would make the grammar messier.
Then we need to be explicitly clear that such cases result in a evaluation. We don't currently have that.
See, for example, , , and . Bash (URL), Elixir (jaxon), and Ruby (jsonpath) all return empty result lists () for $[?(NAME (NAME == true))] $[?((NAME (NAME == true))] and $[?(!(NAME (NAME == true))] On the other hand, I don't think you could find any cases where evaluated to while evaluated to . Not in JSONPath, nor in any of the other good query languages like , , or . That is unique to the draft.
One solution would be to introduce "not a boolean" () analogous to "not a number" (). Semantically invalid boolean expressions, such as would yield . would obey the following laws, where is any boolean or : (The logical operator laws in the spec continue to hold, but only when none of , , or are .) The rule in the spec for filters would still apply: In other words, a semantically invalid boolean expression anywhere inside a filter's boolean expression causes the filter to not to select the current node. (Edited based on feedback from NAME What do you make of this solution? /cc NAME NAME
That would mean an implementation can no longer short-circuit on b && c where b is false. I don’t think there is much point in getting an “exception” semantics for unsupported comparisons. If we do want to do them, they should be actual exception semantics. But the loss of short-circuiting is more important to me than the weirdness of returning false (true) in certain cases. Grüße, Carsten
Yea, good point. I wonder if there is a consistent set of laws which allow both true and to be annihilators for OR and allow both false and to be annihilators for AND? That would at least preserve short-circuiting, but the overall effect would be more difficult to describe as wouldn't then trump everything else.
I'm attracted by the "consistency with JMESPath" argument. I.e. the strings and can replace each other with no change in effect. I thought that's what the draft said. I'd be sympathetic with making the literal string non-well-formed but as Glyn points out, that wouldn't make the need for clarity on the issue go away.
Right. We might even point out that constructs like this could lead to warnings where the API provides a way to do this. Grüße, Carsten
Yes, the behaviour of the current draft for semantically invalid expressions seems to be the same as that of JMESPath after all. The says invalid comparisons (such as ordering operators applied to non-numbers) yield a value which is then treated equivalently to . I thought I'd verify this to be on the safe side. A with the expression evaluated against the JSON: gives the result: whereas gives the result: The evaluator with the expression applied to the same JSON gives the result: whereas the expression gives the result .
EDIT: I think you may be right about this after all. Just not with your example, I believe you are testing with an implementation that does string comparisons differently from the spec (if you're using the online evaluator on the JMESPath site.) Yes Actually, the JMESPath implementation you're using looks like the Python version accessed through the . As the JMESPath author, James Saryerwinnie noted in , "What's going on here is that the spec only defines comparisons as valid on numbers (URL). In 0.9.1, validation was added to enforce this requirement, so things that implicitly worked before now no longer work ... Given this is affecting multiple people I'll add back the support for strings now." James Saryerwinnie went on the suggest that he was going to update the spec as well to allow more general comparisons, but that didn't happen. James actually took a break from JMESPath at around that time to quite recently. Yes. Here it's actually comparing "char" < "char", and returning , not . and gives However, I did the following test with JMESPath.Net with gives gives which I think is consistent with the current draft.
Let's bring this back home. I think we've decided that is not a syntax error. So now we are deciding how it evaluates. The leading proposal is that all of these evaluate to false: - More holistically, any expression which contains an invalid operation evaluates to false. This would imply that an invalid comparison itself does not result in a boolean directly. Rather (as suggested by NAME an implementation would need an internal placeholder (e.g. or ) that is persistent through all subsequent operations and ultimately is coerced to false so that the node is not selected. This would mean that the processing of each of the above expressions is as follows: - observes and evaluates to is coerced to false - observes and evaluates to observes the and evaluates to is coerced to false - observes and evaluates to observes the and evaluates to is coerced to false At best, the use of something like is guidance for an implementation. The specification need only declare that expressions which contain invalid operations (at all) evaluate to false. How a developer accomplishes that is up to them.
Abort evaluation and report an error (the vast majority of existing JSONPath implementations) Abort evaluation and return an empty list (a few existing JSONPath implementations) Continue evaluation while excluding the element affected by the dynamic error (JMESPath specification) All of that sounds complicated and unnecessary. Personally, I don't think any of these constructions are necessary. It is enough to say that in these situations a dynamic error has been detected, and the implementation must react in this way (whatever is decided upon.) As you say "How a developer accomplishes that is up to them."
Ummmm… I was under the impression that was identical to and thus your third item would be true. This seems violently counter-intuitive. We're not going to treat as a syntax error, but we are going to cause its presence to have a nonlocal effect on the entire containing expression. OK, it's back to a careful read of the draft for me.
If you want a nonlocal effect, it is probably best to think in terms of a throw/catch model. (And, yes, I think one needs catch as an explicit mechanism, not just implicitly at the outside of a filter expression.)
If that were the case, then would be true, which isn't right. The expression is still unevaluateable. Similarly with the expression. True in general, but for this specific case, it's the combined with that gives it away, and this can be detected at parse time. Regardless, we need to also consider the case where is true.
I think the crucial thing is that these expressions have well defined semantics so that we can test the behaviour and ensure interoperation. I really don't think we should be encouraging the use of these expressions. My suffers from the lack of short-cutting. If we preserved short-cutting with a similar approach, we'd have to give up commutativity of and and probably some of the other boolean algebra laws. Overall, I think such approaches would make the spec larger and consequently increase its cognitive load for users and implementers alike. Producing a non-local effect is another solution, but I think that too is overly complex for the benefits. If we made the non-local effect that the filter selected no nodes, I think that would be semantically equivalent to my proposal. Also, I don't think we should introduce a try/catch mechanism as the spec complexity would far outweigh the benefits. The current spec: is quite compact in the way it handles the current issue preserves the boolean algebra laws has a precedent in the JMESPath spec. So I propose that we stick with the current spec.
But what's written in the spec doesn't resolve the vs. disparity. Both of these expressions should return an empty node list because both are semantically invalid. As proof of this, is logically equivalent to , which also returns the empty nodelist. If simply and immediately returns , then this equivalent no longer holds.
I wish we could stop using the term "semantically invalid", because that doesn't say what the construct means. A lightweight variant of the catch/throw approach I would prefer would be to say that an expression fails. Constructs that have failing subexpressions also fail. A failing filter expression does not select the item for which the filter expression failed. Disadvantage: Either no short circuiting, or ambiguity with short circuiting. Without short circuiting, one cannot test whether a subsequent subexpression will fail and still get a deterministic result. (I'm not sure we have all we need to make such tests.)
URL makes the current spec clearer: yields false and so yields true and yield false. Where this is a "disparity" or not depends on what semantics we are aiming at. "should" only if we adopt the NaB (or equivalent) or non-local effect semantics. (I agree with NAME that the term "semantically invalid expression" isn't very useful when we are trying to define the semantics of such expressions.) URL defines in terms of to preserve this very equivalence.
I think these disadvantages should steer us away from catch/throw, or equivalent, approaches.
I didn't follow any of this comment. I can't tell what you're saying the spec says vs what you think it should say. Certainly, you're not saying that should be evaluated to true, which would return ALL of the nodes, right? Because that's what it sounds like you're arguing for. Comparisons with booleans don't make sense. Ever. My proof above shows the absurdity of defining one expression to evaluate to false because it, by necessity, means that the converse evaluates to true when neither make sense at all. Call it what you want. I call it "semantically invalid" because that phrase accurately describes that there are no (common) operative semantics that make this expression evaluatable. Sure, if we define it to mean something, then that technically gives it semantic meaning, but in all ordinary cases of how works, this operation doesn't make sense and therefore is, by definition, semantically invalid. There should be one of two outcomes from this issue: It's a parse error. It's a runtime evaluation error. In both cases it's an error; that's not in question here. What kind of error and how it's handled are in question. We've already seen that it doesn't make sense to be a parse error because we need to account for the boolean value coming from the data (i.e. from ). That leaves us with a runtime evaluation error. Whenever a runtime evaluation error occurs, because we've decided that a syntactically valid path always returns, it MUST return an empty nodelist. Therefore, ANY expression that triggers an evaluation error MUST return an empty nodelist. This includes both and . I don't know how to stress this any more. There are no other logical options to resolve this issue.
This reasoning technique is also called proof by lack of imagination (related to the technique "I don't get out much"). I hear that you are arguing for some exception semantics (similar to the catch/throw I brought up). I can sympathize with that, because I also argued for that. However, we are really free to define the expression language as we want, and what we have now is not that much worse than what I was (incompletely) proposing.
(Found the rationale why Erlang does things this way: URL Unfortunately, we can't ask Joe for additional details any more. But apparently these titans in language design for highly reliable systems saw a benefit in a complete total ordering of the type system. JSON does not have a type system, so anything we do will be invention.)
My comment was about the current spec, especially now that PR had landed. The current spec says that yields true. Yes (unless that comparison was part of a large boolean expression which sometimes yielded false) that would return all the nodes. That's precisely what I am arguing for as I think it's the best option. I suspect absurdity, like beauty, is in the eye of the beholder. I can see where you're coming from. I don't follow the chain of reasoning there. The current spec returns, but doesn't always return an empty nodelist. At the risk of repeating myself, the current spec is a logical option in the sense that it is well defined and consistent. I think we are all agreed that none of the options is perfect. All the options under consideration are logical, otherwise we'd rule them out. Some would take more words in the spec to explain and that puts a cognitive load on the user for very little benefit.
NAME It seems your preferred approach is: Let's try to flesh this out a bit. What about shortcutting? For example, what should the behaviour of be? There would appear to be three options: Prescribe an order of evaluation such as "left to right" and allow shortcuts. Result: all nodes are selected. Do not prescribe an order of evaluation and allow shortcuts. Result: non-determinism - all or no nodes are selected, depending on the implementation. (That's bad for testing and interop.) Disallow shortcuts (in which case the order of evaluation doesn't matter). Result: no nodes are selected.
This seems useful to me. The spec is free to specify that inequalities (, >=) are only for numbers. Short-circuit evaluation can be used to avoid errors, e.g.: will return the first array element.
You still have to parse this. And you have to know the value of before you evaluate it. That means you're able to know that this expression is nonsensical (to avoid "invalid") before you evaluate it. Thus shortcutting is moot.
Let's use "undesirable" for expressions we don't like. The example that Glyn provided is just a stand-in for a more complex example that gets true from the JSON value. So the fact that you can parse this and find the undesirable part immediately is not so relevant. With shortcut semantics, the fact that the ignored part of the disjunction is undesirable also is irrelevant. So I do think that Glyn's question is valid.
In JMESPath, evaluates to, not , but "absent", represented in JMESPath by . When "absent" () is evaluated as a boolean according to the JMESPath rules of truth, it gives . It seems consistent that "not absent" is . What would you have if you resolved to the draft's notion of "absent", an empty node list?
Ok, let's try a modified example to avoid the syntactic issue. What should the behaviour of be (where results in a nodelist with a single node with value )? With NAME preferred approach, there would appear to be three options: Prescribe an order of evaluation such as "left to right" and allow short-circuits. Result: all nodes are selected. Do not prescribe an order of evaluation and allow short-circuits. Result: non-determinism - all or no nodes are selected, depending on the implementation. (That's bad for testing and interop.) Disallow short-circuits (in which case the order of evaluation doesn't matter). Result: no nodes are selected.
I'd like to go with the simplest possible thing unless are strong reasons not to. I think the simplest possible thing is: Type-incompatible comparisons yield false and there are no "exceptions" or nonlocal effects. So in this example you unambiguously get and select all the nodes and if you want to optimize by short-circuiting you can do that without fear of confusion or breakage. Type-incompatibility yielding false has the advantage that it's easy to explain in English and, I would argue, usually produces the desired effect. If I am filtering something on and turns out not to be a number - almost certainly an unexpected-data-shape problem - I do not want to match. But if I that with something else then I'm explicitly OK with not getting a match here. Yes, there are still corner-case surprises, for example: is true if is non-numeric, but is not true. I claim that this is at least easy to explain and understand in brief clear language.
A better choice for your argument would have been JS as that was one of the original implementations. Similiar behavior works there (as I discovered testing it in my browser's console). ! and result in false and result in true Interestingly and both result in true for JS. This makes it very apparent that JS is just casting the to and performing the comparison. This kind of implicit casting is something that we have already explicitly decided against. This means that even the JS evaluation isn't a valid argument. I could just as easily use a .Net language or any other strongly typed language to show that doesn't even compile and is therefore nonsensical. Why do we continually compare with this? It's a deviated derivative of JSON Path. Are we trying to align JSON Path with it? To what end? I thought we were trying to define JSON Path, not redefine JMESPath to look like JSON Path. "... develop a standards-track JSONPath specification that is technically sound and complete, based on the common semantics and other aspects of existing implementations. Where there are differences, the working group will analyze those differences and make choices that rough consensus considers technically best, with an aim toward minimizing disruption among the different JSONPath implementations." Yet no one has thought to see what the implementations do in these cases. I would say that the short-cutting is likely preferred, but I don't think my implementation would do it that way. My implementation would evaluate all operands then apply the . This would result in the strange comparison with and so it would select no nodes. C# would shortcut this. and would be considered "side-effect" evaluations and would be skipped. That said, strong typing would guarantee that these would evaluate to types that are valid for , so the resolved expression is guaranteed to make sense in the event shortcutting didn't happen. (In reality, the C# compiler would optimize to a constant , and ultimately the remaining expression wouldn't even make it into the compiled code, so really there's no short-cutting either.)
Well, I chose Erlang because their total ordering is the result of a deliberate design process. Any ordering that you can derive in JavaScript is the result of a haphazard, now universally despised system of implicit conversions — not quite as good an example for my argument as Erlang’s careful design. (Some people appear to have a tendency to derive processing rules for JSON from JavaScript. JSON is an interchange format, which derived its syntax from JavaScript. JSON has no processing rules, and so we have to add some. Any inspiration for the ones we decide to use in JSONPath is as good as any other, as long as it meets the needs of its users.) Grüße, Carsten
For the record, unlike Erland, comparisons in our current draft do not form a (a.k.a. a linear order). A total order satisfies this law: But, according to our current draft, both and are false. Our current draft does, however, provide a since satisfies these laws: That said, in our draft is not a strict partial order which would have to satisfy these laws: In our draft: is false, so is true, which breaks irreflexivity. both and are false, so and are true, which breaks asymmetry. Our current draft does however preserves the laws of boolean algebra (some of which would be broken by the alternatives being discussed in this issue).
And typed comparisons in C# are the result of a deliberate design process. What's your point? You're invoking a selection bias. Our decision to not have type casting (i.e. ) demonstrates that types are important to us. That line of thinking necessitates that any comparison between different types must either error or return false because such a comparison is meaningless. We're not erroring, so we must return false.
Are we now agreed then?
If by in agreement you mean that must also return false.
According to the current draft, returns false and so returns true and thus returns false.
Okay... You're twisting my examples. I want any expression that contains comparisons between types to return an empty nodelist.
I want all items that are not back-ordered or backordered for less than 10 days.
The simplest possible thing would be to do what the vast majority of JSONPath implementations do when a comparison happens that is regarded as not supported, which is to abort evaluation and report a diagnostic. That has the additional advantage of being compatible with the charter "capturing the common semantics of existing implementations", if that still matters. It is also consistent with XPATH 3.1, see (URL) and (URL). The next simplest thing would be to abort evaluation and return an empty list (a few existing JSONPath implementations do that.)
Speaking with my co-chair hat on, I'd like to draw attention to the following text from section 3.1 of the current draft: "The well-formedness and the validity of JSONPath queries are independent of the JSON value the query is applied to; no further errors can be raised during application of the query to a value." I think this has for a long time represented the consensus of the WG. If someone wants to approach the problems raised in this issue by proposing an exception mechanism, that would require a specific proposal covering the details and how best to specify it. Absent such a proposal existing and getting consensus, I think approaches that include a run-time error/exception mechanism are out of bounds.
Co-chair hat off: I could live with that. Among other things, it's easy to describe. It's not my favorite approach but it's sensible.
On 2022-07-20, at 22:15, Tim Bray NAME wrote: Please don’t commingle exceptions with erroring out. Erroring out because of data fed to the expression is not consistent with the above invariant. Exceptions may be processed within the query (e.g., in the classical catch/throw form), and need not violate that invariant. “NaB” is an attempt to add to the data types in such a way that an exception can be handed up the evaluation tree as a return value. Exceptions tend to make the outcome of the query dependent of the sequence in which parts of the query expression are processed, so they may be violating other invariants (which are implicit in some of our minds). Grüße, Carsten
I've been thinking why I keep liking having type-mismatch comparisons be just and made some progress. I think that is a compact way of saying . So if it's not a number, this is unsurprisingly false. If you believe this then it makes perfect sense that if is then is true but is false.
NAME If is , then the spec's statement: implies that is false. Then the spec's statement: implies that , the negation of , is true. Essentially, and give non-intuitive results for non-numeric comparisons. This is surprising and far from ideal. However, because comparisons always produce a boolean value, they can be hedged around with other predicates to get the desired result, e.g. we can replace with which is equivalent to for numeric and is false for non-numeric .
The approach of forcing the filter expression to return an empty nodelist whenever it contains an "undesirable" comparison means that: it is not possible to hedge around "undesirable" comparisons with other predicates, results tend to depend on the order in which subexpressions are evaluated. Let's take an example. Suppose we want to pick out all objects in an array such that the object has a key which is either at least 9 or equal to . For example, in the JSON: With the current spec, the filter has the desired effect and returns the nodelist: The alternative doesn't allow that kind of hedging around. A solution with the alternative approach is to use a list .
Next, let's explore the ordering issue with the alternative approach. What should the behaviour of be (where results in a nodelist with a single node with value )? (Apologies this is for the third time of asking.) With the approach of forcing the filter expression to return an empty nodelist whenever it contains an "undesirable" comparison, there would appear to be three options: Prescribe an order of evaluation such as "left to right" and allow short-circuits. Result: all nodes are selected. Do not prescribe an order of evaluation and allow short-circuits. Result: non-determinism - all or no nodes are selected, depending on the implementation. (That's bad for testing and interop.) Disallow short-circuits (in which case the order of evaluation doesn't matter). Result: no nodes are selected, but the implementation cannot take advantage of the short-circuit as an optimisation. I think option 1 is preferable and probably the most intuitive option. I wonder if there are any other issues (apart from being rather prescriptive) with that option? The boolean algebra laws would only apply in general when "undesirable" comparisons are not present.
I suggest changing this to "…using one of the operators , =. Then… I suggest removing this statement because we've defined , then there's some work to tidy up the definition of == and !=. And in every case, any comparison with a type mismatch is always false.
Actually, with a type mismatch is always true. I was hoping to extend the definition by negation to and , but that seems to create cognitive dissonances.
If you say that type mismatch always yields false, there's no problem, this reduces to true false, which is to say true. You don't need to say anything about order of evaluation. I think if the spec needs to specify order of evaluation, that is a very severe code smell. I'd find that very hard to accept.
No, the rule I'm proposing is simpler: Any comparison with a type mismatch is always false. Which means is not specified as "the opposite of ==" it's specified as meaning "are values of the same non-structured type, and the values are not equal". I can't tell wither and are equal or not if is not a boolean. They are not comparable.
So is always false. Not intuitive to me.
Hmm, having written that, I might have been going overboard. It would be perfectly OK to define as "true if the operands are of different types, or of the same type but not equal" and that would make perfect sense.
We also need to design in an escape clause for structured values, which so far we don't compare.
OK, I think I have probably said enough on this, but I have been doing an unsatisfactory job communicating what seems to me like a reasonably straightforward proposal. Once the spec stabilizes a bit I'd be happy to do a PR. The key language, under "Comparisons" in 3.4.8, would be: True if both operands are of the same type, that type is not a structured type, and the values are equal; otherwise false True if either operand is of a structured type, or if the operands are of different types, or if they are of the same type but the values are unequal; otherwise false. True if both operands are numbers and the first operand is strictly greater than the second; otherwise false. True if both operands are numbers and the first operand is strictly less than the second; otherwise false. True if both operands are numbers and the first operand is greater than or equal to the second; otherwise false. True if both operands are numbers and the first operand is less than or equal to the second; otherwise false.
That approach, like the current draft, preserves the laws of boolean algebra and therefore the order of evaluation doesn't matter. It gets rid of some nasty surprises (such as ) which are present in the current draft. It seems that is the negation of , in which case it's probably simpler to define it as such. However, the approach is not completely free of surprises because it breaks: the converse relationship between and that if and only if not the converse relationship between and that if and only if not as well as: the reflexivity law (see above, e.g. is false) the strongly connected or total law (see above, e.g. it is not true that either or since both are false). Thus and would no longer be partial orders and and would no longer be strict total orders when these four operators are considered as binary relations over the whole set of values. We could rationalise this by thinking of these four operators as orderings of numbers with non-numbers added as "unordered" extensions.
Eh, I like writing each one out in clear English rather than having them depend on each other. But, editorial choice. It's still true if a & b are both numbers. If you accept that "<" means "A is a number AND B is a number AND A<B" then I think all the logical relationships work out properly. Anyhow, it would be helpful at this point if someone could offer the WG a summary of what the coherently-proposed options are that are available to us to finish off this issue? I will if nobody else leaps in, but I'm not neutral on the issue.
Agreed. Only if A and B are numbers! For example, let , then is false but is true. Thus is false in this case.
Let me try: . . The NaB proposal, but with left-to-right evaluation and short-circuiting. The option of forcing a total order among all values like Erlang does. (I think options 2-5 also need the current spec wording about paths which result in an empty nodelist, about which I think there is consensus.)
The option of requiring an empty node list if any type-mismatch comparisons occur anywhere in the expression. [right?]
That's certainly a coherent option, so yes. But please note I don't think anyone actually proposed this, although I may have misunderstood NAME I thought his idea was to skip the current item if there was a type-mismatch (this has the same external semantics as the NaB proposal). (If there was a static type mismatch, then clearly the result would be an empty node list.)
On reflection, I think my preferred option is number 2: : It is consistent with how we treat paths which result in empty node lists in and comparisons, i.e. the comparison yields false and the effect is local. It is consistent with how we treat equality comparisons of structured values. Again, the comparison yields false and the effect is local. It preserves the laws of boolean algebra. It also treats , , , and the same when applied to non-numeric values, unlike the current draft.
Option 2 is not my preferred option, but I sure could live with that.
What is?
I probably would have gone towards full symmetry of and . I'm also not sure the simple approach we now have is optimal in the face of future extensions (catch/throw is more like that). But we also need to make sure we align with existing implementations, and keep some simplicity.
NAME It is noted though that (1) - (4) make the specification broadly incompatible with all JSONPath implementations that are represented in the . For example, most JSONPath implementations support string comparisons with , , , and , including all of the Javascript ones, and Java Jayway, see . Proposals (1)-(4) would produce results that users of these implementations might find surprising. In addition, a few of the JSONPath implementations support array comparisons with , and , including perhaps the most widely used implementation of all, Java Jayway, see , and . Users coming from a Jayway background might find these proposed rules surprising, and, in fact, a regression. Users of other implementations that don't currently support a comparison might expect a diagnostic message, as that is how the vast majority of current implementations behave, rather than a result list of questionable usefulness. It is noted that a few implementations return an empty result list if encountering a comparison deemed invalid. But none exhibit behaviour as proposed in (1) - (4). It seems to this commentator that the credible alternatives, assuming some connection with prior work is still considered desirable, are Abort and report a diagnostic if a comparison deemed invalid is detected (the behaviour of all but a few implementations) Abort and return an empty result list if a comparison deemed invalid is detected (the behaviour of a few implementations) Define a complete ordering over JSON values Regarding the last alternative, I'm not familiar with what Erlang does, but there is considerable prior work on defining complete orderings over JSON values, it's a common requirement that JSON values have to be comparable. There's a degree of arbitrariness involved, when values of different types are to be compared, but similar conventions have been adopted. EDIT: Jayway supports array comparisons with , and , but not with , , , and as I originally said.
Yep. Simple solution: Provide for string comparisons. This is complicated if one accepts a lot of diverse user requirements, but maybe we are just going for compatibility with the installed base. This is becoming ugly for maps, but even that can be done.
For an existence proof URL Note that map and tuple ordering in Erlang are optimized for speed, not for minimal surprise. We would probably want to go for per-element/per-ordered-entry comparison.
I agree that strings should be comparable. Does anyone have a good counter-argument? As previously noted, up till now the WG has not favored the abort/diagnostics approach, but is free to change its mind (I don't have any opinions on this). If the WG wants to define orderings on all the JSON types, that doesn't seem insane. But neither does stopping at numbers and strings. Hmm, all the JSON types? What about true/false/null?
The counter argument was that this is hard to do well, so it didn't fit into a MVP. But it is easy to sort Unicode Scalar Values. I continue to prefer avoiding that -- this is simply part of the transition to an industrial strength spec. Yes! (See Erlang link above.)
It's arbitrary, but the usual approach is to associate ordinal numbers with each type (null,true,false,string,number,array,object) and compare the numbers when the types are different. I originally took that approach from . Not everyone agrees with that, for example, this considers it an error if types differ, see (URL).
Here's my order: null < false < true < number < string < array < map Numbers are compared as per their I-JSON interpretation. Strings and arrays are ordered lexicographically (in the computer science, not the lexicographical sense -- we're probably not going for URL). Ordering between two maps works by ordering the keys within each map first, and then ordering them like arrays made up of the map entries in the form [key, value, key, value, ...].
NAME Looks okay. I would have expressed it as comparing two sequences of name-value pairs (ordered by name) but I think that's equivalent.
I could live with total ordering of all JSON values, particularly because we then get the full set of ordering laws, which allow safe refactoring of expressions: the converse relationship between and that if and only if not , the converse relationship between and that if and only if not , various other laws (see ). My main reservation with this approach is the size/complexity of the spec relative to the value to users, but let's see what it would take to nail down the edge cases. I'd be grateful if someone could define the kind of lexicographic ordering of UNICODE strings which is being suggested. (Sorry if it's obvious to everyone else. I'm fuzzy about how encodings and escapes would be dealt with. And what about strings outside I-JSON?) What is the ordering for numbers outside I-JSON? What is the ordering for objects outside I-JSON (specifically those with duplicate keys)?
A text string is an array of Unicode Scalar Values. Sort like arrays. Mathematical ordering always works. Just wanted to relax this to I-JSON. Well, again, implementations should not have to handle this. But you'll notice that if someone wants to make this extension, you just need to add "order map entries by key, then by value" to the algorithm I gave.
There are lots of ways to get lost in details cough code points cough normal forms but it’s very helpful to know that if you sort UTF-8 streams treating them as unsigned byte lists, you are also sorting by Unicode Scalar Value, which is the right thing to do.
NAME Yes. It's not just a “reservation”, it’s a huge issue. And looking at the thread, it seems clear to me that imposing such an ordering involves decisions that seem whimsical and arbitrary, based on philosophy or maybe theology. Which is to say open to endless debate. At this point I am in favor of ordering strings but will not argue if it seems others would rather not. I am becoming pretty strongly against an attempt to specify universal JSON cross-type value ordering or really any attempt to compare structured type values. I suggest we leave that for a future WG. I imagine , , and friends that implement a universal-type-comparability framework, specified by people who aren't us.
On 25. Jul 2022, at 07:36, Tim Bray NAME wrote: (Which we don’t do) Yes. But don’t say that too loud, or you will get the attention of people who think that JSON strings should instead be sorted by UTF-16 code units. (Yes, the non-IETF “JSON Canonicalization Scheme”, RFC 8785, does this. You can’t make this stuff up.) I think it is easy to get consensus on ordering by Unicode Scalar Values, and the fact that UTF-8 was designed to be order-preserving then is a nice topping for that obvious decision. Grüße, Carsten
I just wrote up the entire spec for that in an issue comment. The “size” or “complexity” is not an issue. That depends mostly on the ability of the WG to actually decide. I think you are underselling us here. You are overstating this — this is not universal, just JSON. And, I’m sorry, but this stuff is my pay grade. This has been done before (Erlang example — and see how short that spec is!), and the JSON generic data model fortunately is even simpler. Re structured values: JSON has only two kinds, and arrays are a no-brainer. Maps are a bit more work, but non-surprising ways to order maps can be specified in a sentence or two. I think we have seen it demonstrated that not specifying this leads to pain. Grüße, Carsten
If we can compare structured values using and , the it seems churlish not to compare them using (since, in a total ordering, implies ). I like that, but since we've held off comparing structured values for equality for so long, perhaps someone will remember what the difficulty was, in which case wouldn't the same difficulty also apply to and ?
We just didn't want to do the work, thinking that an MVP could be done without that. After agonizing for a while on the effects of a partial ordering, it is now clear that going for a total ordering is the path of less resistance.
Ok. Let's see how a total ordering plays out and whether we can gain consensus for that. I wonder if we should fold paths returning an empty nodelist into the total ordering. If returns an empty node list, the current draft says, for all other than a path resulting in an empty node list: and yield false, and and yield false. These, of course, break the total ordering laws. Perhaps instead we should extend the proposed total order to: path-returning-an-empty-nodelist < null < false < true < number < string < array < map
Or, to make the phrasing a bit less unwieldy, absent < null < false < true < number < string < array < map
Having slept on this, and read these proposals for universal JSON value ordering, I am very strongly against applying / to anything but strings, numbers, and //. I am very strongly against applying /// to anything but numbers and strings. The notion that someone in a sane engineering role would write flabbergasts me. (Assume the struct on the right comes from a reference not a literal.) Any such ordering is essentially arbitrary and I fail to see how it could produce a useful or even safe result. Here's a thing I've learned while working in large-scale event processing. When you get this kind of type mismatch in a real application of JSONPath, it is almost always because unexpected data came across the wire. Something is broken. True-story example: Teams changed numbers to digits-in-quotes in messages because they really needed 264 range, and were surprised when the downstream broke. They were lucky* the downstream broke because who can tell what business disaster would have ensued if their filter had admitted bogus data because, to quote the above proposal, . This makes me shudder. Yes, the spec would be simpler and more mathematically tidy if we pretended that there were meaningful ordering relationships between fish and bicycles. Please let’s not.
Total ordering between mismatched types is a big stretch for me too. What is the purpose of having an ordering between different types within a query language anyway? We have other tools like JSONSchema to support us in ensuring the types of the values are what the JSONPath expects them to be. Sane engineers should be using a defined schema to make this sort of question go away anyway, regardless of whether the WG decides to support it. By the same token, ordering between arrays I'm fairly OK with as long as the types of corresponding elements match. However the inequalities and between maps may seem like a logical extension but JSON maps are explicitly unordered. Any attempt to order them should be resisted IMO. Consider the following made up example: The charges since the last bill (i.e. the last two recentcharges array elements) might be retrieved as follows: The following, if allowed, could give a wrong result against the data above (e.g. if using sorted member names):
The possibility that someone might try to exploit ordered comparisons of maps or of arrays of non-uniformly typed items (e.g. makes me feel slightly ill. I've seen programmers (including myself when I was starting out) do some pretty stupid things, and exploiting such comparisons unfortunately seems rather likely. I agree that would not be a good thing to allow or encourage. The main thing the total order had going for it was safe refactoring (in the sense that expressions can be manipulated according to the normal total ordering laws and the behaviour will not change). But option 2 (of making undesirable ordered comparisons yield false), even if it does break some refactorings, has a certain simplicity about it and will probably prevent people deliberately exploiting such comparisons. (Regardless of which option we choose, we could certainly allow ordered comparisons of strings, but that's pretty much orthogonal to the choice of option.)
What about comparing array containing numbers and strings? For those cases where there may be a value encoded as a string (e.g. ISO8601 dates).
I guess that makes sense when the types in the arrays being compared happen to line up. But I'm not convinced the benefit of defining general array comparisons is really worth the conceptual overhead in the spec.
NAME > I am very strongly against applying /// to anything but numbers and strings. All of glyn's proposals (1)-(5) define /// for all kinds of values. In which case you might want a diagnostic, like . Or the vast majority of existing JSONPath implementations.
On 2022-07-26, at 14:44, Daniel Parker NAME wrote: OK, so the new consensus is that we do want JSONPath to be able to break? (Not just syntax errors before the query meets data, but also data errors?) Grüße, Carsten
In the case of XPath 3.1, errors are raised or not, depending on a mode. Please let's avoid that. I personally don't think there is a convincing case for raising data errors. I would be open to raising optional, implementation-dependendant warnings. But I'm also comfortable with continuing the current design point of raising syntax errors based on the path alone and then absorbing any kind of anomaly in the data without failing the request.
NAME Not even if, in Tim's words, "Something is broken"? Seriously broken? Proposals (1)-(5) all perform a common purpose: to take the comparisons that are less sensible, and resolve them to something, for completeness. Something is returned, and for all the proposals, that may be a non-empty result list, depending on the query. But consider a user playing around with an online query tool. Which result is more helpful? The result or the diagnostic The expression either side of operator ">" must evaluate to numeric or string values
I agree that diagnostics are great for attended situations, but for unattended scenarios, I'd prefer the behaviour to be robust.
NAME For "unattended scenarios" we have error logs and alerts.
Yes, I'm comfortable with logging as a side effect of a robust operation (no failure after syntax checking the path).
Co-chair hat off: While I do not have a strong opinion about diagnostics vs exceptions vs silent-false, I do note that this kind of type-mismatch problem is a corner case and I wonder if it justifies the investment of a whole bunch of WG effort. Co-chair hat back on: As I said, I think the current draft does represent a fairly long-held consensus of the WG. If we want to introduce a diagnostic/exception mechanism, it's not going to happen without a PR to make the option concrete as opposed to abstract. So if you want such a thing, arguing for it in this thread is not going to get us there.
So, one imagines something like: "A JSONPath implementation MUST NOT cause a failure or exception as the consequence of applying a well-formed JSONPath to well-formed JSON data. An implementation SHOULD provide a mechanism whereby software can receive report of run-time errors such as type-incompatible comparisons." Hmm, I don't see anything in the draft spec about what happens if you try to apply a JSONPath and what you're trying to apply it to is broken, i.e. not valid JSON. Maybe we're in a world where callers have to live with failed applications anyhow?
I'm not "arguing for it", I don't care what the authors of the draft eventually put in it. I'm simply providing comments or feedback on matters that may be helpful to the authors in thinking about the issues, or not. They can decide. There used to be more people doing that, but my impression is that most of them have dropped off.
I think that deserves its own issue: URL
I'd like to address this issue, so, unless someone someone objects strongly, I'll put together a PR for option 2 plus string comparisons. That won't necessarily be the end of the discussion, but at least it will give us a concrete alternative to the current draft.
Please do, and thanks in advance. We will do better if we have concrete texts before us to shape our discussion.
On 2022-07-19, at 15:55, Glyn Normington NAME wrote: The other reason that would speak against an option is that it lacks expressiveness, i.e., it is impossible to express a query that can be expected to be needed. I am specifically thinking about queries that first look at a data item to see what it is and then make a condition based on what was found. Grüße, Carsten
On 2022-07-20, at 22:23, Tim Bray NAME wrote: … which reminds us that this cannot currently be explicitly expressed. Grüße, Carsten
I think there may be room for further fine-tuning but at this point I'd like to merge this and get a nice editors' draft that we can read end-to-end.

Related discussion:
text: consisting of a single node, each such path is replaced by the value of its node and then: <del> comparison using one of the operators "==" yields true if and only if the comparison is between equal primitive values. </del> <ins> a comparison using the operator "==" yields true if and only if the comparison is between values of the same primitive type (numbers, strings, booleans, and "null") which are equal. </ins> <del> comparisons using one of the operators "<=" or ">=" yield true if and only if the comparison is between numeric values which satisfy the comparison. </del> <ins> a comparison using the operator "!=" yields true if and only if the comparison is not between values of the same primitive type (numbers, strings, booleans, and "null") which are equal. </ins> <del> any comparison of two values using one of the operators "!=", ">", "<" is defined as the negation of the comparison of the same values using the operator "==", "<=", ">=", respectively. </del> <ins> a comparison using one of the operators "<", "<=", ">", or ">=" yields true if and only if the comparison is between values of the same type which are both numbers or both strings and which satisfy the comparison: </ins> <del> Note that "==" comparisons between a structured value and any value, including the same structured value, yield false.  Consequently, "!=" comparisons between a structured value and any value, including the same structured value, yield true. </del> <ins> numbers in the I-JSON RFC7493 range of exact values MUST compare using the normal mathematical ordering; one or both numbers outside that range MAY compare using an implementation specific ordering the empty string compares less than any non-empty string a non-empty string compares less than another non-empty string if and only if the first string starts with a lower Unicode scalar value than the second string or if both strings start with the same Unicode scalar value and the remainder of the first string compares less than the remainder of the second string. Note that "==" comparisons between a structured value (that is, an object or an array) and any value, including the same structured value, yield false and "!=" comparisons between a structured value and any value, including the same structured value, yield true. </ins> 3.4.7.2.2.1.