From e7a37e58a53490534b7ec741bfc59c9efc0a7aca Mon Sep 17 00:00:00 2001 From: Apoorv Darshan Date: Thu, 9 Jul 2026 22:04:42 +0530 Subject: [PATCH] Fix ordering comparison of mixed number/string operands Ordering operators (`<`, `<=`, `>`, `>=`) are only valid when both operands are in the same comparable category. `_is_comparable` was applied to each operand independently, so a number and a string both passed the guard and were then compared directly, raising `TypeError: '<' not supported between instances of 'int' and 'str'` instead of yielding null. Require both operands to be numbers or both to be strings before comparing; otherwise return None, matching the spec and the behavior already used for other incomparable types. Fixes #169 --- jmespath/visitor.py | 14 +++++++++----- tests/compliance/boolean.json | 23 ++++++++++++++++++++++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/jmespath/visitor.py b/jmespath/visitor.py index 15fb1774..d9fbba0b 100644 --- a/jmespath/visitor.py +++ b/jmespath/visitor.py @@ -35,12 +35,18 @@ def _is_special_number_case(x, y): return isinstance(x, bool) -def _is_comparable(x): +def _is_comparable(x, y): + # Ordering operators are only valid when both operands + # belong to the same comparable category, i.e. both are + # numbers or both are strings. Mixing categories (e.g. a + # number and a string) is not comparable and yields null. + # # The spec doesn't officially support string types yet, # but enough people are relying on this behavior that # it's been added back. This should eventually become # part of the official spec. - return _is_actual_number(x) or isinstance(x, string_type) + return ((_is_actual_number(x) and _is_actual_number(y)) or + (isinstance(x, string_type) and isinstance(y, string_type))) def _is_actual_number(x): @@ -151,9 +157,7 @@ def visit_comparator(self, node, value): # will yield a None value. left = self.visit(node['children'][0], value) right = self.visit(node['children'][1], value) - num_types = (int, float) - if not (_is_comparable(left) and - _is_comparable(right)): + if not _is_comparable(left, right): return None return comparator_func(left, right) diff --git a/tests/compliance/boolean.json b/tests/compliance/boolean.json index dd7ee588..891494c4 100644 --- a/tests/compliance/boolean.json +++ b/tests/compliance/boolean.json @@ -220,7 +220,8 @@ "two": 2, "three": 3, "emptylist": [], - "boolvalue": false + "boolvalue": false, + "stringvalue": "2" }, "cases": [ { @@ -267,6 +268,26 @@ "expression": "one < boolvalue", "result": null }, + { + "expression": "one < stringvalue", + "result": null + }, + { + "expression": "one <= stringvalue", + "result": null + }, + { + "expression": "one > stringvalue", + "result": null + }, + { + "expression": "one >= stringvalue", + "result": null + }, + { + "expression": "stringvalue < one", + "result": null + }, { "expression": "one < two && three > one", "result": true