diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 2677f7d18f..579109d11c 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -366,6 +366,9 @@ class TestFilters(IntegrationTestCase): self.assertEqual(get_safe_filters('[["name", "=", "ABC"]]'), [["name", "=", "ABC"]]) # FrappeClient encodes scalar filters via frappe.as_json — must still unwrap self.assertEqual(get_safe_filters('"ABC"'), "ABC") + self.assertIsNone(get_safe_filters("null")) + self.assertIs(get_safe_filters("true"), True) + self.assertIs(get_safe_filters("false"), False) def test_get_safe_filters_passes_through_non_strings(self): self.assertEqual(get_safe_filters({"name": "ABC"}), {"name": "ABC"}) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index d20a754114..3f3b88b2f1 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -904,13 +904,16 @@ def call(fn, *args, **kwargs): def get_safe_filters(filters): - if isinstance(filters, str) and filters and filters[0] in '{["': - try: - return orjson.loads(filters) - except (TypeError, ValueError): - # filters are not passed, not json - pass - return filters + try: + parsed = orjson.loads(filters) + except (TypeError, ValueError): + # not a string, or not valid json + return filters + # numeric JSON is ambiguous: docnames like "3E002" parse as floats and + # would be corrupted by stringifying back, so keep the original string + if isinstance(parsed, int | float) and not isinstance(parsed, bool): + return filters + return parsed def create_batch(iterable: Iterable, size: int) -> Generator[Iterable]: