* 📡 Document a duplicate-object-key rejection recipe RFC 8259 leaves handling of duplicate object keys to the implementation; this library silently keeps only the last value for a repeated key. Discussion #5085 asked for an opt-in rejection mode. Decision: don't change library behavior, but document the existing parser-callback workaround instead. Adds a "Recipe: rejecting duplicate object keys" section to parser_callbacks.md, adapted from a community-contributed workaround. Fixed an off-by-one bug in the original snippet: object_start reports the depth of the object's parent, while key events inside that object report depth+1, so indexing the per-depth key set with the same depth in both places caused an out-of-bounds access on nested objects. Verified the published snippet compiles and behaves correctly for flat duplicates, nested duplicates, sibling objects sharing key names, and arrays of objects. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Cross-link the duplicate-key recipe with the existing object_t behavior docs object_t.md and features/types/index.md already document that duplicate object keys resolve to an unspecified value (RFC 8259 leaves this to the implementation). The new recipe's intro overstated this as a guaranteed "last value wins" rule, which isn't true in general -- parsing text keeps the last value, but constructing from an initializer list keeps the first. Reworded the recipe to point at object_t's "unspecified" behavior instead of asserting a specific rule, and added cross-links from both existing pages to the new recipe. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Turn the duplicate-key recipe into a standalone, compiled example Replace the inline code fence in the "rejecting duplicate object keys" recipe with a proper docs/mkdocs/docs/examples/*.cpp + .output pair, included via --8<-- like every other example on the site. The .output file was generated by running it through the project's actual example build (docs/Makefile: single_include, -std=c++11, -DJSON_USE_GLOBAL_UDLS=0) and cross-checked with `make check_output`, and the source passes the pinned astyle 3.4.13 formatting unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
4.9 KiB
nlohmann::basic_json::object_t
using object_t = ObjectType<StringType,
basic_json,
default_object_comparator_t,
AllocatorType<std::pair<const StringType, basic_json>>>;
The type used to store JSON objects.
RFC 8259 describes JSON objects as follows:
An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array.
To store objects in C++, a type is defined by the template parameters described below.
Template parameters
ObjectType- the container to store objects (e.g.,
std::maporstd::unordered_map) StringType- the type of the keys or names (e.g.,
std::string). The comparison functionstd::less<StringType>is used to order elements inside the container. AllocatorType- the allocator to use for objects (e.g.,
std::allocator)
Notes
Default type
With the default values for ObjectType (std::map), StringType (std::string), and AllocatorType
(std::allocator), the default value for object_t is:
// until C++14
std::map<
std::string, // key_type
basic_json, // value_type
std::less<std::string>, // key_compare
std::allocator<std::pair<const std::string, basic_json>> // allocator_type
>
// since C++14
std::map<
std::string, // key_type
basic_json, // value_type
std::less<>, // key_compare
std::allocator<std::pair<const std::string, basic_json>> // allocator_type
>
See default_object_comparator_t for more information.
Behavior
The choice of object_t influences the behavior of the JSON class. With the default type, objects have the following
behavior:
- When all names are unique, objects will be interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings.
- When the names within an object are not unique, it is unspecified which one of the values for a given key will be
chosen. For instance,
#!json {"key": 2, "key": 1}could be equal to either#!json {"key": 1}or#!json {"key": 2}. To reject duplicate keys instead of silently resolving them one way or another, see this parsing recipe. - Internally, name/value pairs are stored in lexicographical order of the names. Objects will also be serialized (see
dump) in this order. For instance,#!json {"b": 1, "a": 2}and#!json {"a": 2, "b": 1}will be stored and serialized as#!json {"a": 2, "b": 1}. - When comparing objects, the order of the name/value pairs is irrelevant. This makes objects interoperable in the sense
that they will not be affected by these differences. For instance,
#!json {"b": 1, "a": 2}and#!json {"a": 2, "b": 1}will be treated as equal.
Limits
RFC 8259 specifies:
An implementation may set limits on the maximum depth of nesting.
In this class, the object's limit of nesting is not explicitly constrained. However, a maximum depth of nesting may be
introduced by the compiler or runtime environment. A theoretical limit can be queried by calling the
max_size function of a JSON object.
Storage
Objects are stored as pointers in a basic_json type. That is, for any access to object values, a pointer of type
object_t* must be dereferenced.
Object key order
The order name/value pairs are added to the object are not preserved by the library. Therefore, iterating an object
may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in
alphabetical order as std::map with std::less is used by default. Please note this behavior conforms to
RFC 8259, because any order implements the specified "unordered" nature of JSON
objects.
Cross-basic_json conversion requirements
When converting an object from one basic_json specialization to another via the
converting constructor, the target object_t's key_type must be
directly constructible from the source basic_json's string_t type (or more generally, from the
source object's key type). If this requirement is not met, the conversion does not fail; instead,
the object is silently converted as an array of key-value pairs, which is incorrect. See
issue #3425 for details and an example.
Examples
??? example
The following code shows that `object_t` is by default, a typedef to `#!cpp std::map<json::string_t, json>`.
```cpp
--8<-- "examples/object_t.cpp"
```
Output:
```json
--8<-- "examples/object_t.output"
```
Version history
- Added in version 1.0.0.