From 865ce2dbcbdc9ec625611da272d63a81ca211d86 Mon Sep 17 00:00:00 2001 From: simpleqt <2015900818@qq.com> Date: Wed, 4 Nov 2026 06:27:54 +0800 Subject: [PATCH] fix(functions): report the resolved key in sort_by type errors `sort_by` validates the type of the sort key, but when the key resolves to something other than a number or a string it raised JMESPathTypeError with the *array element* as the offending value: >>> jmespath.search('sort_by(@, &k)', [{'k': [1, 2]}]) JMESPathTypeError: In function sort_by(), invalid type for value: {'k': [1, 2]}, expected one of: ['string', 'number'], received: "array" The signature in the specification is sort_by(array elements, expression->number|expression->string expr) so the value the constraint applies to is the value `expr` resolved to, not the element it was resolved against. The reported value was also inconsistent with the reported type: the message claimed to have received an "array" while showing an object. `min_by` and `max_by`, which share the same signature, already report the resolved key; this makes `sort_by` consistent with them: >>> jmespath.search('sort_by(@, &k)', [{'k': [1, 2]}]) JMESPathTypeError: In function sort_by(), invalid type for value: [1, 2], expected one of: ['string', 'number'], received: "array" The type check now goes through the same key function that performs the sort, so the key is resolved exactly once for the type check and once per sort comparison, as before. Error types, error values for keys that are already valid, and all successful results are unchanged. Signed-off-by: simpleqt <2015900818@qq.com> --- jmespath/functions.py | 15 ++++++++++----- tests/test_functions.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/jmespath/functions.py b/jmespath/functions.py index 627b569d..38dfa0c9 100644 --- a/jmespath/functions.py +++ b/jmespath/functions.py @@ -316,14 +316,19 @@ def _func_sort_by(self, array, expref): # either a string of a number. We then create a key function # that validates that type, which requires that remaining array # elements resolve to the same type as the first element. + # The type check is done on the resolved key, not on the array + # element, because that's the value the signature constrains. + keyfunc = self._create_key_func(expref, + ['number', 'string'], + 'sort_by') + required_key = keyfunc(array[0]) required_type = self._convert_to_jmespath_type( - type(expref.visit(expref.expression, array[0])).__name__) + type(required_key).__name__) if required_type not in ['number', 'string']: raise exceptions.JMESPathTypeError( - 'sort_by', array[0], required_type, ['string', 'number']) - keyfunc = self._create_key_func(expref, - [required_type], - 'sort_by') + 'sort_by', required_key, required_type, + ['string', 'number']) + keyfunc = self._create_key_func(expref, [required_type], 'sort_by') return list(sorted(array, key=keyfunc)) @signature({'types': ['array']}, {'types': ['expref']}) diff --git a/tests/test_functions.py b/tests/test_functions.py index a352d69e..0dfad824 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -55,3 +55,13 @@ def test_variadic_is_pluralized(self): self.assertEqual( str(exception), 'Expected at least 1 argument for function not_null(), received 0') + + def test_sort_by_reports_the_resolved_key_in_type_errors(self): + # The spec defines sort_by as sort_by(array, expression->number| + # expression->string), so the value that has the invalid type is the + # value the expression resolved to, not the array element it was + # resolved against. min_by/max_by already report the resolved key. + with self.assertRaises(exceptions.JMESPathTypeError) as e: + jmespath.search('sort_by(@, &k)', [{'k': [1, 2]}]) + self.assertIn('invalid type for value: [1, 2]', str(e.exception)) + self.assertIn('received: "array"', str(e.exception))