diff --git a/CHANGELOG.md b/CHANGELOG.md index 0497ac3508..cc74f549ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ This document records all notable changes to [HTTPie](https://httpie.io). This project adheres to [Semantic Versioning](https://semver.org/). +## [3.2.5-dev](https://github.com/httpie/cli/compare/3.2.4...master) (unreleased) + +- Fixed nested JSON keys that look like numbers but are not in canonical form (e.g. `007`, `1_000`, `+5`) being silently normalized instead of kept verbatim. + ## [3.2.4](https://github.com/httpie/cli/compare/3.2.3...3.2.4) (2024-11-01) - Fix default certs loading and unpin `requests`. ([#1596](https://github.com/httpie/cli/issues/1596)) diff --git a/httpie/cli/nested_json/parse.py b/httpie/cli/nested_json/parse.py index 323a22eef1..84f018fec2 100644 --- a/httpie/cli/nested_json/parse.py +++ b/httpie/cli/nested_json/parse.py @@ -138,7 +138,7 @@ def send_buffer() -> Iterator[Token]: kind = TokenKind.TEXT if not backslashes: for variation, kind in [ - (int, TokenKind.NUMBER), + (parse_number, TokenKind.NUMBER), (check_escaped_int, TokenKind.TEXT), ]: try: @@ -178,6 +178,22 @@ def can_advance() -> bool: yield from send_buffer() +def parse_number(value: str) -> int: + """Parse ``value`` as an integer, but only when it is written in its + canonical form. + + Unlike the built-in ``int()``, this rejects representations such as + ``007`` (leading zeros), ``1_000`` (digit-group separators), ``+5`` + (explicit sign) or values padded with whitespace. Keeping those as plain + text preserves numeric-looking object keys verbatim instead of silently + normalizing them (e.g. ``007`` used to become ``7``). + """ + number = int(value) + if value != str(number): + raise ValueError('Not a canonical integer') + return number + + def check_escaped_int(value: str) -> str: if not value.startswith(BACKSLASH): raise ValueError('Not an escaped int') diff --git a/tests/test_json.py b/tests/test_json.py index e758ebe7f4..04f4df6f82 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -439,6 +439,15 @@ def test_complex_json_arguments_with_non_json(httpbin, request_type, value): '2': {'3': {'4': 5}}, }, ), + ( + # Numeric-looking keys are only treated as array indexes when + # written in their canonical form. Non-canonical variants (leading + # zeros, digit separators, explicit signs, surrounding whitespace) + # are kept verbatim as object keys instead of being normalized by + # `int()` (e.g. `007` must not become `7`). + ['007=1', '00=2', '1_000=3', '+5=4', 'x[007]=6'], + {'007': '1', '00': '2', '1_000': '3', '+5': '4', 'x': {'007': '6'}}, + ), ( [':={"foo": {"bar": "baz"}}', 'top=val'], {'': {'foo': {'bar': 'baz'}}, 'top': 'val'},