Files
json/docs/mkdocs/docs/features/parsing/sax_interface.md
T
Niels Lohmann 2aecd86b37 Document the contract of object keys passed to json_sax::key and key callbacks (#5956)
* 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 <mail@nlohmann.me>

* Add a test for the object key contract and correct the BON8 note

Pin the guarantees documented for json_sax::key and the key event of
parser callbacks: decoded names, validation before delivery, delivery
before the colon, one event per repeated name, and the binary formats.
BON8 rejects ill-formed UTF-8 in a name even with sax_parse, so the
note no longer lists it with the formats that pass bytes through.

See discussion #5955.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the GCC build of the object key contract test

GCC 11 in C++11 mode rejects a default argument on a lambda parameter
(-Wc++14-extensions) and ignores a static_cast<void> on the
warn_unused_result of parse(). Pass the input format explicitly and bind
the parse result to a variable.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Add version history for the object key contract

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-29 07:37:33 +01:00

5.8 KiB

SAX Interface

The library uses a SAX-like interface with the following functions:

classDiagram

class sax_t ["json::sax_t"] {
    <<interface>>
    +bool null()*
    
    +bool boolean(bool val)*
    
    +bool number_integer(number_integer_t val)*
    +bool number_unsigned(number_unsigned_t val)*
    
    +bool number_float(number_float_t val, const string_t& s)*
    
    +bool string(string_t& val)*
    +bool binary(binary_t& val)*
    
    +bool start_object(std::size_t elements)*
    +bool end_object()*
    +bool start_array(std::size_t elements)*
    +bool end_array()*
    +bool key(string_t& val)*
    
    +bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex)*
}

The interface `json::sax_t`: one function per event, each returning `bool`; the functions marked with `*` are pure virtual.

// called when null is parsed
bool null();

// called when a boolean is parsed; value is passed
bool boolean(bool val);

// called when a signed or unsigned integer number is parsed; value is passed
bool number_integer(number_integer_t val);
bool number_unsigned(number_unsigned_t val);

// called when a floating-point number is parsed; value and original string is passed
bool number_float(number_float_t val, const string_t& s);

// called when a string is parsed; value is passed and can be safely moved away
bool string(string_t& val);
// called when a binary value is parsed; value is passed and can be safely moved away
bool binary(binary_t& val);

// called when an object or array begins or ends, resp. The number of elements is passed (or -1 if not known)
bool start_object(std::size_t elements);
bool end_object();
bool start_array(std::size_t elements);
bool end_array();
// called when an object key is parsed; value is passed and can be safely moved away
bool key(string_t& val);

// called when a parse error occurs; byte position, the last token, and an exception is passed
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);

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 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:

input

{"a":[1,true],"b":"x"}

events, in the order the parser calls them

  1. start_object(-1)
  2. key("a")
  3. start_array(-1)
  4. number_unsigned(1)
  5. boolean(true)
  6. end_array()
  7. key("b")
  8. string("x")
  9. end_object()

handler: build a value tree, as parse does

{
  "a": [
    1,
    true
  ],
  "b": "x"
}

After end_object() the tree is complete and no container is open. The number of elements is passed as -1: the parser does not know it in advance.

The three lanes show the input consumed so far, the events called so far, and the state of the selected handler (*build a tree* or *keep only /b*). Check *return false at this event* to stop the parse at the current event and see which events are no longer called.

To implement your own SAX handler, proceed as follows:

  1. Implement the SAX interface in a class. You can use class nlohmann::json_sax<json> as base class, but you can also use any class where the functions described above are implemented and public.
  2. Create an object of your SAX interface class, e.g. my_sax.
  3. Call #!cpp bool json::sax_parse(input, &my_sax); where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.

Note the sax_parse function only returns a #!cpp bool indicating the result of the last executed SAX event. It does not return json value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your parse_error implementation. Internally, the SAX interface is used for the DOM parser (class json_sax_dom_parser) as well as the acceptor (json_sax_acceptor), see file json_sax.hpp.

See also