From cf8b7c8b8d7ce3b31149c4d71e74a58225b5dc69 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 20:29:43 +0100 Subject: [PATCH] Document the contract of object keys passed to json_sax::key and key callbacks Describe what the name passed to json_sax::key and to a parser callback for parse_event_t::key holds when parsing JSON text: escapes are decoded to UTF-8, different spellings of a name yield identical strings, the name is validated before the call (invalid escapes or UTF-8 give a parse error and no key event), no normalization or case conversion is applied, the event precedes the ':' check, and duplicate names are reported once per occurrence. Also note that sax_parse passes binary-format keys unchanged. See discussion #5955. Signed-off-by: Niels Lohmann --- .../docs/api/basic_json/parser_callback_t.md | 9 +++++++++ docs/mkdocs/docs/api/json_sax/key.md | 16 ++++++++++++++++ .../docs/features/parsing/sax_interface.md | 3 +++ 3 files changed, 28 insertions(+) diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md index a12634b9d..643610cb2 100644 --- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md +++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md @@ -63,6 +63,15 @@ called: Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In the latter case, it is skipped completely, or replaced by `null` if it is the top-level value. +## Notes + +For a `parse_event_t::key` event, `parsed` holds the decoded object name, with the same guarantees as the `val` +parameter of [`json_sax::key`](../json_sax/key.md#notes): all escape sequences are decoded and `\uXXXX` escapes are +stored as UTF-8, so different spellings of the same name yield identical strings; the name is validated completely +before the callback is called, so a name with an invalid escape sequence or ill-formed UTF-8 causes a parse error and +no `key` event; no Unicode normalization or case conversion is applied; and a name that occurs several times in the +same object yields one `key` event per occurrence. + ## Examples ??? example "Example: skip an object key while parsing" diff --git a/docs/mkdocs/docs/api/json_sax/key.md b/docs/mkdocs/docs/api/json_sax/key.md index 591f590e0..a92d4230e 100644 --- a/docs/mkdocs/docs/api/json_sax/key.md +++ b/docs/mkdocs/docs/api/json_sax/key.md @@ -19,6 +19,22 @@ Whether parsing should proceed. It is safe to move the passed object key value. +When parsing JSON text, `val` is the decoded object name, not its spelling in the input: + +- All escape sequences (`\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t`, `\uXXXX`, and surrogate pairs) are + decoded, and `\uXXXX` escapes are stored as UTF-8. Different spellings of the same name, such as `#!json "/"` + and `#!json "\/"`, or `#!json "\u00e9"` and `#!json "\u00E9"`, yield identical strings. An escaped `\u0000` yields + an embedded NUL byte. +- The name is scanned and validated completely before `key` is called. If it contains an invalid escape sequence, an + unpaired surrogate, a control character, or ill-formed UTF-8, the parse fails with a + [`parse_error`](../../home/exceptions.md#parse-errors), and `key` is not called for it. +- No Unicode normalization or case conversion is applied: names that differ in any byte are different strings. +- `key` is called before the following `:` is read, so a parse error may still follow for the same member. +- A name that occurs several times in the same object is passed once per occurrence. + +For the binary formats, object keys contain no escape sequences: `val` holds the bytes stored in the input. +[`sax_parse`](../basic_json/sax_parse.md) passes them unchanged, without checking them for valid UTF-8. + ## Examples ??? example diff --git a/docs/mkdocs/docs/features/parsing/sax_interface.md b/docs/mkdocs/docs/features/parsing/sax_interface.md index 872105a20..f732f4b2e 100644 --- a/docs/mkdocs/docs/features/parsing/sax_interface.md +++ b/docs/mkdocs/docs/features/parsing/sax_interface.md @@ -64,6 +64,9 @@ bool parse_error(std::size_t position, const std::string& last_token, const json The return value of each function determines whether parsing should proceed. +Object keys are passed to `key` decoded and validated: escape sequences are already resolved, so different spellings of +the same name yield the same string. See [`key`](../../api/json_sax/key.md#notes) for the exact guarantees. + ## Stepping through the events The events for a small input, step by step, and what two different handlers do with them. Press *Play* or step with *Next*: