Repository navigation
fix(serde): integer map keys must be exactly a JSON integer - #249
Open
yuxi-liu-wired wants to merge 2 commits into
Open
yuxi-liu-wired wants to merge 2 commits into
yuxi-liu-wired wants to merge 2 commits into
Conversation
yuxi-liu-wired
force-pushed
the
pr/numeric-map-keys
branch
from
October 1, 2026 20:23
28e798e to
a18df46
Compare
yuxi-liu-wired
force-pushed
the
pr/numeric-map-keys
branch
from
October 10, 2026 08:37
a18df46 to
a4fc254
Compare
Map keys deserialized as i8..i64, u8..u64 or f64 went through
`deserialize_number`, which skips leading whitespace, so whitespace
inside the key's quotes was accepted and dropped:
from_str::<BTreeMap<u32, u8>>(r#"{" 1":2}"#) Ok({1: 2})
from_str::<BTreeMap<i64, u8>>("{\"\t1\":2}") Ok({1: 2})
although `"\t1"` is not even a valid JSON string (raw control
character), and `" 1"` is not the text of a number. serde_json rejects
both ("expected key to be a number in quotes"), and sonic-rs's own
i128/u128 key path already did.
The single-argument arm of `deserialize_numeric_key!` now makes the same
check as the two-argument arm: the first byte inside the quotes must be
a digit or `-`.
Test: `test_numeric_key_rejects_leading_whitespace` (fails before this
change).
Found by a three-way differential fuzzer (serde_json, simd-json,
sonic-rs).
`MapKeyDeserializer` (used by `from_value` and `Value` as a
`Deserializer`) parsed an integer key as a prefix of the key text and
ignored the rest:
let v: Value = from_str(r#"{"12abc":1,"1,2":2,"08":3}"#)?;
from_value::<BTreeMap<i64, u8>>(&v) Ok({12: 1, 1: 2, 0: 3})
Deserializing the same JSON text into the same type is an error, and so
is serde_json's `from_value`. After the number, the key must be at its
end; otherwise `ExpectedNumericKey`.
Test: `test_numeric_map_key_from_value_must_be_whole_number` (fails
before this change).
Found by a differential fuzzer (serde_json, simd-json, sonic-rs, jiter).
yuxi-liu-wired
force-pushed
the
pr/numeric-map-keys
branch
from
October 11, 2026 06:17
a4fc254 to
9109ca0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related fixes in one area, 2 commits (each with its own test).
fix: reject whitespace before a numeric map key
Map keys deserialized as i8..i64, u8..u64 or f64 went through
deserialize_number, which skips leading whitespace, so whitespaceinside the key's quotes was accepted and dropped:
although
"\t1"is not even a valid JSON string (raw controlcharacter), and
" 1"is not the text of a number. serde_json rejectsboth ("expected key to be a number in quotes"), and sonic-rs's own
i128/u128 key path already did.
The single-argument arm of
deserialize_numeric_key!now makes the samecheck as the two-argument arm: the first byte inside the quotes must be
a digit or
-.Test:
test_numeric_key_rejects_leading_whitespace(fails before thischange).
fix: numeric map keys from a Value must be the whole key
MapKeyDeserializer(used byfrom_valueandValueas aDeserializer) parsed an integer key as a prefix of the key text andignored the rest:
Deserializing the same JSON text into the same type is an error, and so
is serde_json's
from_value. After the number, the key must be at itsend; otherwise
ExpectedNumericKey.Test:
test_numeric_map_key_from_value_must_be_whole_number(failsbefore this change).
Testing
Each commit adds a regression test that fails before the change. The full test suite passes and
cargo fmt --checkis clean on the changed files.Found with a differential fuzzer comparing serde_json, simd-json, sonic-rs and jiter.