From dda9087a68f2a388cf7ec6046714f9df4a5f1d51 Mon Sep 17 00:00:00 2001 From: gaoflow Date: Wed, 17 Jun 2026 07:42:32 +0200 Subject: [PATCH 1/2] Parse signed hexadecimal literals instead of raising `loads('-0x1f')` (and the `+` form) raised `ValueError: invalid literal for int() with base 10` because the hex-prefix check in `_walk_ast` only looked at the start of the raw token and missed a leading sign. Strip an optional leading `+`/`-` before testing for the `0x`/`0X` prefix so signed hex values go through `parse_int(..., base=16)` like their unsigned counterparts. Added cases to the numbers test (`+0xf`, `-0xf`, `-0XABCD`, `-0x0`). --- README.md | 5 +++++ json5/lib.py | 3 ++- tests/lib_test.py | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index df037b9..ab24aa2 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,11 @@ $ git push --tags origin ## Version History / Release Notes +* v0.14.1 (unreleased) + * Fixed parsing of signed hexadecimal literals such as `-0x1f` and + `+0xff`, which previously raised a `ValueError` instead of parsing + to an integer. + * v0.14.0 (2026-03-27) This is really just a dependency bump release. * No (non-test) code changes. diff --git a/json5/lib.py b/json5/lib.py index c6c82d1..fa9d8ed 100644 --- a/json5/lib.py +++ b/json5/lib.py @@ -360,7 +360,8 @@ def _walk_ast( return False ty, v = el if ty == 'number': - if v.startswith('0x') or v.startswith('0X'): + unsigned = v[1:] if v[:1] == '-' else v + if unsigned.startswith('0x') or unsigned.startswith('0X'): return parse_int(v, base=16) if '.' in v or 'e' in v or 'E' in v: return parse_float(v) diff --git a/tests/lib_test.py b/tests/lib_test.py index 72b85da..d0d6597 100644 --- a/tests/lib_test.py +++ b/tests/lib_test.py @@ -102,6 +102,12 @@ def test_numbers(self): self.check('0x123456', 1193046) self.check_fail('0x+', ':1 Unexpected "+" at column 3') + # signed hex literals + self.check('+0xf', 15) + self.check('-0xf', -15) + self.check('-0XABCD', -43981) + self.check('-0x0', 0) + # floats self.check('1.5', 1.5) self.check('1.5e3', 1500.0) From e0572004f8636e2f6d00f5b7cb4b41dc4490a17e Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Fri, 19 Jun 2026 21:40:06 +0200 Subject: [PATCH 2/2] Address signed hex parser review nit --- json5/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json5/lib.py b/json5/lib.py index fa9d8ed..15b33e9 100644 --- a/json5/lib.py +++ b/json5/lib.py @@ -360,7 +360,7 @@ def _walk_ast( return False ty, v = el if ty == 'number': - unsigned = v[1:] if v[:1] == '-' else v + unsigned = v[1:] if v.startswith('-') else v if unsigned.startswith('0x') or unsigned.startswith('0X'): return parse_int(v, base=16) if '.' in v or 'e' in v or 'E' in v: