diff options
Diffstat (limited to 'docs/handbook/json4cpp')
| -rw-r--r-- | docs/handbook/json4cpp/architecture.md | 613 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/basic-usage.md | 601 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/binary-formats.md | 411 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/building.md | 430 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/code-style.md | 209 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/custom-types.md | 465 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/element-access.md | 581 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/exception-handling.md | 368 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/iteration.md | 339 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/json-patch.md | 341 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/json-pointer.md | 361 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/overview.md | 330 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/parsing-internals.md | 493 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/performance.md | 275 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/sax-interface.md | 337 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/serialization.md | 528 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/testing.md | 190 | ||||
| -rw-r--r-- | docs/handbook/json4cpp/value-types.md | 474 |
18 files changed, 7346 insertions, 0 deletions
diff --git a/docs/handbook/json4cpp/architecture.md b/docs/handbook/json4cpp/architecture.md new file mode 100644 index 0000000000..d0140b8bbf --- /dev/null +++ b/docs/handbook/json4cpp/architecture.md @@ -0,0 +1,613 @@ +# json4cpp — Architecture + +## Overview + +The json4cpp library (nlohmann/json 3.12.0) is organized as a heavily +templatized, header-only C++ library. The architecture revolves around a single +class template, `basic_json`, whose template parameters allow customization of +every underlying storage type. This document describes the internal structure, +class hierarchy, memory layout, and key design patterns. + +## The `basic_json` Class Template + +### Template Declaration + +The full template declaration in `include/nlohmann/json_fwd.hpp`: + +```cpp +template< + template<typename U, typename V, typename... Args> class ObjectType = std::map, + template<typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, + class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template<typename U> class AllocatorType = std::allocator, + template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer, + class BinaryType = std::vector<std::uint8_t>, + class CustomBaseClass = void +> +class basic_json; +``` + +Each parameter controls a specific aspect: + +| Parameter | Purpose | Default | +|---|---|---| +| `ObjectType` | Map template for JSON objects | `std::map` | +| `ArrayType` | Sequential container for JSON arrays | `std::vector` | +| `StringType` | String type for keys and string values | `std::string` | +| `BooleanType` | Boolean storage | `bool` | +| `NumberIntegerType` | Signed integer type | `std::int64_t` | +| `NumberUnsignedType` | Unsigned integer type | `std::uint64_t` | +| `NumberFloatType` | Floating-point type | `double` | +| `AllocatorType` | Allocator template | `std::allocator` | +| `JSONSerializer` | Serializer template for custom types | `adl_serializer` | +| `BinaryType` | Container for binary data | `std::vector<std::uint8_t>` | +| `CustomBaseClass` | Optional base class for extension | `void` | + +### Default Type Aliases + +Two default specializations are defined: + +```cpp +using json = basic_json<>; +using ordered_json = basic_json<nlohmann::ordered_map>; +``` + +The `ordered_json` type preserves insertion order by using `ordered_map` +instead of `std::map`. + +### Derived Type Aliases + +Within `basic_json`, the following public type aliases expose the actual +types used for JSON value storage: + +```cpp +using object_t = ObjectType<StringType, basic_json, + default_object_comparator_t, + AllocatorType<std::pair<const StringType, basic_json>>>; +using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; +using string_t = StringType; +using boolean_t = BooleanType; +using number_integer_t = NumberIntegerType; +using number_unsigned_t = NumberUnsignedType; +using number_float_t = NumberFloatType; +using binary_t = nlohmann::byte_container_with_subtype<BinaryType>; +using object_comparator_t = detail::actual_object_comparator_t<basic_json>; +``` + +The `default_object_comparator_t` depends on the C++ standard level: +- C++14 and above: `std::less<>` (transparent comparator) +- C++11: `std::less<StringType>` + +## Inheritance Structure + +### Base Class: `json_base_class` + +`basic_json` inherits from `detail::json_base_class<CustomBaseClass>`: + +```cpp +class basic_json + : public ::nlohmann::detail::json_base_class<CustomBaseClass> +``` + +When `CustomBaseClass` is `void` (the default), this is an empty base class +that adds no overhead. When a user-provided type is specified, it becomes +the base, enabling extension without modifying the library. + +### Friend Declarations + +The class declares friendship with its internal collaborators: + +```cpp +template<detail::value_t> friend struct detail::external_constructor; +template<typename> friend class ::nlohmann::json_pointer; +template<typename BasicJsonType, typename InputType> + friend class ::nlohmann::detail::parser; +friend ::nlohmann::detail::serializer<basic_json>; +template<typename BasicJsonType> + friend class ::nlohmann::detail::iter_impl; +template<typename BasicJsonType, typename CharType> + friend class ::nlohmann::detail::binary_writer; +template<typename BasicJsonType, typename InputType, typename SAX> + friend class ::nlohmann::detail::binary_reader; +template<typename BasicJsonType, typename InputAdapterType> + friend class ::nlohmann::detail::json_sax_dom_parser; +template<typename BasicJsonType, typename InputAdapterType> + friend class ::nlohmann::detail::json_sax_dom_callback_parser; +friend class ::nlohmann::detail::exception; +``` + +## Memory Layout: `json_value` Union + +### The `json_value` Union + +The core storage is a union that keeps the `basic_json` object at minimum +size: + +```cpp +union json_value +{ + object_t* object; // pointer — 8 bytes + array_t* array; // pointer — 8 bytes + string_t* string; // pointer — 8 bytes + binary_t* binary; // pointer — 8 bytes + boolean_t boolean; // typically 1 byte + number_integer_t number_integer; // 8 bytes + number_unsigned_t number_unsigned; // 8 bytes + number_float_t number_float; // 8 bytes + + json_value() = default; + json_value(boolean_t v) noexcept; + json_value(number_integer_t v) noexcept; + json_value(number_unsigned_t v) noexcept; + json_value(number_float_t v) noexcept; + json_value(value_t t); // creates empty container for compound types + + void destroy(value_t t); // type-aware destructor +}; +``` + +**Key design decisions:** + +1. **Pointers for variable-length types.** Objects, arrays, strings, and binaries + are stored as pointers. This keeps the union at 8 bytes on 64-bit systems + and avoids calling constructors/destructors for the union members of + non-active types. + +2. **Value semantics for scalars.** Booleans, integers, and floats are stored + directly in the union without indirection. + +3. **Heap allocation via `create<T>()`.** The private static method + `basic_json::create<T>(Args...)` uses the `AllocatorType` to allocate + and construct heap objects. + +### The `data` Struct + +The union is wrapped in a `data` struct that pairs it with the type tag: + +```cpp +struct data +{ + value_t m_type = value_t::null; + json_value m_value = {}; + + data(const value_t v); + data(size_type cnt, const basic_json& val); + data() noexcept = default; + data(data&&) noexcept = default; + + ~data() noexcept { m_value.destroy(m_type); } +}; +``` + +The instance lives in `basic_json` as `data m_data`: + +```cpp +data m_data = {}; // the type + value + +#if JSON_DIAGNOSTICS +basic_json* m_parent = nullptr; // parent pointer for diagnostics +#endif + +#if JSON_DIAGNOSTIC_POSITIONS +std::size_t start_position = std::string::npos; +std::size_t end_position = std::string::npos; +#endif +``` + +### Destruction Strategy + +The `json_value::destroy(value_t)` method handles recursive destruction +without stack overflow. For arrays and objects, it uses an iterative +approach with a `std::vector<basic_json>` stack: + +```cpp +void destroy(value_t t) { + // For arrays/objects: flatten children onto a heap-allocated stack + if (t == value_t::array || t == value_t::object) { + std::vector<basic_json> stack; + // Move children to stack + while (!stack.empty()) { + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + // Move current_item's children to stack + // current_item safely destructed here (no children) + } + } + // Deallocate the container itself + switch (t) { + case value_t::object: /* deallocate object */ break; + case value_t::array: /* deallocate array */ break; + case value_t::string: /* deallocate string */ break; + case value_t::binary: /* deallocate binary */ break; + default: break; + } +} +``` + +This prevents stack overflow when destroying deeply nested JSON structures. + +## The `value_t` Enumeration + +Defined in `detail/value_t.hpp`: + +```cpp +enum class value_t : std::uint8_t +{ + null, // null value + object, // unordered set of name/value pairs + array, // ordered collection of values + string, // string value + boolean, // boolean value + number_integer, // signed integer + number_unsigned, // unsigned integer + number_float, // floating-point + binary, // binary array + discarded // discarded by parser callback +}; +``` + +A comparison operator defines a Python-like ordering: +`null < boolean < number < object < array < string < binary` + +With C++20, this uses `std::partial_ordering` via the spaceship operator. + +## Class Invariant + +The `assert_invariant()` method (called at the end of every constructor) +enforces the following: + +```cpp +void assert_invariant(bool check_parents = true) const noexcept +{ + JSON_ASSERT(m_data.m_type != value_t::object || m_data.m_value.object != nullptr); + JSON_ASSERT(m_data.m_type != value_t::array || m_data.m_value.array != nullptr); + JSON_ASSERT(m_data.m_type != value_t::string || m_data.m_value.string != nullptr); + JSON_ASSERT(m_data.m_type != value_t::binary || m_data.m_value.binary != nullptr); +} +``` + +When `JSON_DIAGNOSTICS` is enabled, it additionally checks that all children +have their `m_parent` pointer set to `this`. + +## Internal Component Architecture + +### Input Pipeline + +``` +Input Source → Input Adapter → Lexer → Parser → DOM / SAX Events +``` + +1. **Input Adapters** (`detail/input/input_adapters.hpp`) + - `file_input_adapter` — wraps `std::FILE*` + - `input_stream_adapter` — wraps `std::istream` + - `iterator_input_adapter` — wraps iterator pairs + - `contiguous_input_adapter` — optimized for contiguous memory + +2. **Lexer** (`detail/input/lexer.hpp`) + - `lexer_base<BasicJsonType>` — defines `token_type` enumeration + - `lexer<BasicJsonType, InputAdapterType>` — the tokenizer + - Token types: `literal_true`, `literal_false`, `literal_null`, + `value_string`, `value_unsigned`, `value_integer`, `value_float`, + `begin_array`, `begin_object`, `end_array`, `end_object`, + `name_separator`, `value_separator`, `parse_error`, `end_of_input` + +3. **Parser** (`detail/input/parser.hpp`) + - `parser<BasicJsonType, InputAdapterType>` — recursive descent parser + - Supports callback-based filtering via `parser_callback_t` + - Supports both DOM parsing and SAX event dispatch + +4. **SAX Interface** (`detail/input/json_sax.hpp`) + - `json_sax<BasicJsonType>` — abstract base with virtual methods + - `json_sax_dom_parser` — builds a DOM tree from SAX events + - `json_sax_dom_callback_parser` — DOM builder with filtering + +### Output Pipeline + +``` +basic_json → Serializer → Output Adapter → Destination +``` + +1. **Serializer** (`detail/output/serializer.hpp`) + - `serializer<BasicJsonType>` — converts JSON to text + - Handles indentation, UTF-8 validation, number formatting + - `error_handler_t`: `strict`, `replace`, `ignore` for invalid UTF-8 + +2. **Binary Writer** (`detail/output/binary_writer.hpp`) + - `binary_writer<BasicJsonType, CharType>` — writes CBOR, MessagePack, + UBJSON, BJData, BSON + +3. **Output Adapters** (`detail/output/output_adapters.hpp`) + - `output_vector_adapter` — writes to `std::vector<CharType>` + - `output_stream_adapter` — writes to `std::ostream` + - `output_string_adapter` — writes to a string type + +### Iterator System + +``` +basic_json::iterator → iter_impl<basic_json> + → internal_iterator (union of object/array/primitive iterators) +``` + +- `iter_impl<BasicJsonType>` — the main iterator class +- `internal_iterator<BasicJsonType>` — holds the active iterator: + - `typename object_t::iterator object_iterator` for objects + - `typename array_t::iterator array_iterator` for arrays + - `primitive_iterator_t` for scalars (0 = begin, 1 = end) +- `json_reverse_iterator<Base>` — reverse iterator adapter +- `iteration_proxy<IteratorType>` — returned by `items()`, exposes + `key()` and `value()` methods + +### Conversion System + +The ADL (Argument-Dependent Lookup) design enables seamless integration of +user-defined types: + +``` +User Type → to_json(json&, const T&) → json value +json value → from_json(const json&, T&) → User Type +``` + +- `adl_serializer<T>` — default serializer that delegates via ADL +- `detail/conversions/to_json.hpp` — built-in `to_json()` overloads + for standard types (arithmetic, strings, containers, pairs, tuples) +- `detail/conversions/from_json.hpp` — built-in `from_json()` overloads + +### JSON Pointer and Patch + +- `json_pointer<RefStringType>` — implements RFC 6901, stores parsed + reference tokens as `std::vector<string_t>` +- Patch operations implemented directly in `basic_json::patch_inplace()` + as an inline method operating on the `basic_json` itself + +## The `ordered_map` Container + +Defined in `include/nlohmann/ordered_map.hpp`: + +```cpp +template<class Key, class T, class IgnoredLess = std::less<Key>, + class Allocator = std::allocator<std::pair<const Key, T>>> +struct ordered_map : std::vector<std::pair<const Key, T>, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector<std::pair<const Key, T>, Allocator>; + + std::pair<iterator, bool> emplace(const key_type& key, T&& t); + T& operator[](const key_type& key); + T& at(const key_type& key); + size_type erase(const key_type& key); + size_type count(const key_type& key) const; + iterator find(const key_type& key); + // ... +}; +``` + +It inherits from `std::vector` and implements map-like operations with +linear search. The `IgnoredLess` parameter exists for API compatibility +with `std::map` but is not used — instead, `std::equal_to<>` (C++14) or +`std::equal_to<Key>` (C++11) is used for key comparison. + +## The `byte_container_with_subtype` Class + +Wraps binary data with an optional subtype tag for binary formats +(MsgPack ext types, CBOR tags, BSON binary subtypes): + +```cpp +template<typename BinaryType> +class byte_container_with_subtype : public BinaryType +{ +public: + using container_type = BinaryType; + using subtype_type = std::uint64_t; + + void set_subtype(subtype_type subtype_) noexcept; + constexpr subtype_type subtype() const noexcept; + constexpr bool has_subtype() const noexcept; + void clear_subtype() noexcept; + +private: + subtype_type m_subtype = 0; + bool m_has_subtype = false; +}; +``` + +## Namespace Organization + +The library uses inline namespaces for ABI versioning: + +```cpp +NLOHMANN_JSON_NAMESPACE_BEGIN // expands to: namespace nlohmann { inline namespace ... { +// ... +NLOHMANN_JSON_NAMESPACE_END // expands to: } } +``` + +The inner inline namespace name encodes configuration flags to prevent +ABI mismatches when different translation units are compiled with +different macro settings. The `detail` sub-namespace is not part of the +public API. + +## Template Metaprogramming Techniques + +### SFINAE and Type Traits + +Located in `detail/meta/type_traits.hpp`, these traits control overload +resolution: + +- `is_basic_json<T>` — checks if T is a `basic_json` specialization +- `is_compatible_type<BasicJsonType, T>` — checks if T can be stored +- `is_getable<BasicJsonType, T>` — checks if `get<T>()` works +- `has_from_json<BasicJsonType, T>` — checks for `from_json()` overload +- `has_non_default_from_json<BasicJsonType, T>` — non-void return version +- `is_usable_as_key_type<Comparator, KeyType, T>` — for heterogeneous lookup +- `is_comparable<Comparator, A, B>` — checks comparability + +### Priority Tags + +The `get_impl()` method uses priority tags (`detail::priority_tag<N>`) +to control overload resolution order: + +```cpp +template<typename ValueType> +ValueType get_impl(detail::priority_tag<0>) const; // standard from_json +template<typename ValueType> +ValueType get_impl(detail::priority_tag<1>) const; // non-default from_json +template<typename BasicJsonType> +BasicJsonType get_impl(detail::priority_tag<2>) const; // cross-json conversion +template<typename BasicJsonType> +basic_json get_impl(detail::priority_tag<3>) const; // identity +template<typename PointerType> +auto get_impl(detail::priority_tag<4>) const; // pointer access +``` + +Higher priority tags are preferred during overload resolution. + +### External Constructors + +The `detail::external_constructor<value_t>` template specializations +handle constructing `json_value` instances for specific types: + +```cpp +template<> struct external_constructor<value_t::string>; +template<> struct external_constructor<value_t::number_float>; +template<> struct external_constructor<value_t::number_unsigned>; +template<> struct external_constructor<value_t::number_integer>; +template<> struct external_constructor<value_t::array>; +template<> struct external_constructor<value_t::object>; +template<> struct external_constructor<value_t::boolean>; +template<> struct external_constructor<value_t::binary>; +``` + +## Diagnostics Architecture + +### `JSON_DIAGNOSTICS` Mode + +When enabled, each `basic_json` node stores a `m_parent` pointer: + +```cpp +#if JSON_DIAGNOSTICS +basic_json* m_parent = nullptr; +#endif +``` + +The `set_parents()` and `set_parent()` methods maintain these links. +On errors, `exception::diagnostics()` walks the parent chain to build +a JSON Pointer path showing where in the document the error occurred: + +``` +[json.exception.type_error.302] (/config/debug) type must be boolean, but is string +``` + +### `JSON_DIAGNOSTIC_POSITIONS` Mode + +When enabled, byte offsets from parsing are stored: + +```cpp +#if JSON_DIAGNOSTIC_POSITIONS +std::size_t start_position = std::string::npos; +std::size_t end_position = std::string::npos; +#endif +``` + +Error messages then include `(bytes N-M)` indicating the exact input range. + +## Copy and Move Semantics + +### Copy Constructor + +Deep-copies the value based on type. For compound types (object, array, +string, binary), the heap-allocated data is cloned: + +```cpp +basic_json(const basic_json& other) + : json_base_class_t(other) +{ + m_data.m_type = other.m_data.m_type; + switch (m_data.m_type) { + case value_t::object: m_data.m_value = *other.m_data.m_value.object; break; + case value_t::array: m_data.m_value = *other.m_data.m_value.array; break; + case value_t::string: m_data.m_value = *other.m_data.m_value.string; break; + // ... scalar types are copied directly + } + set_parents(); +} +``` + +### Move Constructor + +Transfers ownership and invalidates the source: + +```cpp +basic_json(basic_json&& other) noexcept + : json_base_class_t(std::forward<json_base_class_t>(other)), + m_data(std::move(other.m_data)) +{ + other.m_data.m_type = value_t::null; + other.m_data.m_value = {}; + set_parents(); +} +``` + +### Copy-and-Swap Assignment + +Uses the copy-and-swap idiom for exception safety: + +```cpp +basic_json& operator=(basic_json other) noexcept { + using std::swap; + swap(m_data.m_type, other.m_data.m_type); + swap(m_data.m_value, other.m_data.m_value); + json_base_class_t::operator=(std::move(other)); + set_parents(); + return *this; +} +``` + +## Comparison Architecture + +### C++20 Path (Three-Way Comparison) + +When `JSON_HAS_THREE_WAY_COMPARISON` is true: + +```cpp +bool operator==(const_reference rhs) const noexcept; +bool operator!=(const_reference rhs) const noexcept; +std::partial_ordering operator<=>(const_reference rhs) const noexcept; +``` + +### Pre-C++20 Path + +Individual comparison operators are defined as `friend` functions: + +```cpp +friend bool operator==(const_reference lhs, const_reference rhs) noexcept; +friend bool operator!=(const_reference lhs, const_reference rhs) noexcept; +friend bool operator<(const_reference lhs, const_reference rhs) noexcept; +friend bool operator<=(const_reference lhs, const_reference rhs) noexcept; +friend bool operator>(const_reference lhs, const_reference rhs) noexcept; +friend bool operator>=(const_reference lhs, const_reference rhs) noexcept; +``` + +Both paths use the `JSON_IMPLEMENT_OPERATOR` macro internally, which handles: +1. Same-type comparison (delegates to underlying type's operator) +2. Cross-numeric-type comparison (int vs float, signed vs unsigned) +3. Unordered comparison (NaN, discarded values) +4. Different-type comparison (compares `value_t` ordering) + +## `std` Namespace Specializations + +The library provides: + +```cpp +namespace std { + template<> struct hash<nlohmann::json> { ... }; + template<> struct less<nlohmann::detail::value_t> { ... }; + void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept; // pre-C++20 only +} +``` + +The hash function delegates to `nlohmann::detail::hash()` which recursively +hashes the JSON value based on its type. diff --git a/docs/handbook/json4cpp/basic-usage.md b/docs/handbook/json4cpp/basic-usage.md new file mode 100644 index 0000000000..80b9b3a176 --- /dev/null +++ b/docs/handbook/json4cpp/basic-usage.md @@ -0,0 +1,601 @@ +# json4cpp — Basic Usage + +## Including the Library + +```cpp +#include <nlohmann/json.hpp> + +// Convenience alias +using json = nlohmann::json; +``` + +Or with the forward declaration header (for header files): + +```cpp +#include <nlohmann/json_fwd.hpp> // declares json, ordered_json, json_pointer +``` + +## Creating JSON Values + +### Null + +```cpp +json j; // default constructor → null +json j = nullptr; // explicit null +json j(nullptr); // explicit null +json j(json::value_t::null); // from value_t enum +``` + +### Boolean + +```cpp +json j = true; +json j = false; +json j(json::value_t::boolean); // false (default-initialized) +``` + +### Numbers + +```cpp +// Integer (stored as number_integer_t = std::int64_t) +json j = 42; +json j = -100; + +// Unsigned (stored as number_unsigned_t = std::uint64_t) +json j = 42u; +json j = static_cast<std::uint64_t>(100); + +// Floating-point (stored as number_float_t = double) +json j = 3.14; +json j = 1.0e10; +``` + +### String + +```cpp +json j = "hello world"; +json j = std::string("hello"); + +// With C++17 string_view: +json j = std::string_view("hello"); +``` + +### Array + +```cpp +// From initializer list +json j = {1, 2, 3, 4, 5}; + +// Explicit array factory +json j = json::array(); // empty array +json j = json::array({1, 2, 3}); // pre-populated + +// From value_t enum +json j(json::value_t::array); // empty array + +// From count and value +json j(5, "x"); // ["x", "x", "x", "x", "x"] +``` + +### Object + +```cpp +// From initializer list of key-value pairs +json j = { + {"name", "Alice"}, + {"age", 30}, + {"active", true} +}; + +// Explicit object factory +json j = json::object(); +json j = json::object({{"key", "value"}}); + +// From value_t enum +json j(json::value_t::object); + +// The library auto-detects objects vs arrays in initializer lists: +// All elements are [string, value] pairs → object +// Otherwise → array +json obj = {{"a", 1}, {"b", 2}}; // → object +json arr = {1, 2, 3}; // → array +json arr2 = {{1, 2}, {3, 4}}; // → array of arrays +``` + +### Binary + +```cpp +// Binary data without subtype +json j = json::binary({0x01, 0x02, 0x03}); + +// Binary data with subtype (used by MessagePack ext, CBOR tags, etc.) +json j = json::binary({0x01, 0x02}, 42); + +// From std::vector<std::uint8_t> +std::vector<std::uint8_t> data = {0xCA, 0xFE}; +json j = json::binary(data); +json j = json::binary(std::move(data)); // move semantics +``` + +### From Existing Types + +The `basic_json` constructor template accepts any "compatible type" — +any type for which a `to_json()` overload exists: + +```cpp +// Standard containers +std::vector<int> v = {1, 2, 3}; +json j = v; // [1, 2, 3] + +std::map<std::string, int> m = {{"a", 1}, {"b", 2}}; +json j = m; // {"a": 1, "b": 2} + +// Pairs and tuples (C++11) +std::pair<std::string, int> p = {"key", 42}; +json j = p; // ["key", 42] + +// Enum types (unless JSON_DISABLE_ENUM_SERIALIZATION is set) +enum Color { Red, Green, Blue }; +json j = Green; // 1 +``` + +## Parsing JSON + +### From String + +```cpp +// Static parse method +json j = json::parse(R"({"key": "value", "number": 42})"); + +// From std::string +std::string input = R"([1, 2, 3])"; +json j = json::parse(input); + +// User-defined literal (requires JSON_USE_GLOBAL_UDLS or using namespace) +auto j = R"({"key": "value"})"_json; +``` + +### From Stream + +```cpp +#include <fstream> + +std::ifstream file("data.json"); +json j = json::parse(file); + +// Or with stream extraction operator: +json j; +file >> j; +``` + +### From Iterator Pair + +```cpp +std::string input = R"({"key": "value"})"; +json j = json::parse(input.begin(), input.end()); + +// Works with any input iterator +std::vector<char> data = ...; +json j = json::parse(data.begin(), data.end()); +``` + +### Parse Options + +```cpp +json j = json::parse( + input, + nullptr, // callback (nullptr = no callback) + true, // allow_exceptions (true = throw on error) + false, // ignore_comments (false = comments are errors) + false // ignore_trailing_commas (false = trailing commas are errors) +); +``` + +### Error Handling During Parsing + +```cpp +// Option 1: Exceptions (default) +try { + json j = json::parse("invalid json"); +} catch (json::parse_error& e) { + std::cerr << e.what() << "\n"; + // [json.exception.parse_error.101] parse error at line 1, column 1: + // syntax error while parsing value - invalid literal; ... +} + +// Option 2: No exceptions +json j = json::parse("invalid json", nullptr, false); +if (j.is_discarded()) { + // parsing failed +} +``` + +### Validation Without Parsing + +```cpp +bool valid = json::accept(R"({"key": "value"})"); // true +bool invalid = json::accept("not json"); // false + +// With options +bool valid = json::accept(input, true, true); // ignore comments, trailing commas +``` + +### Parser Callbacks + +Filter or modify values during parsing: + +```cpp +json j = json::parse(input, [](int depth, json::parse_event_t event, json& parsed) { + // event: object_start, object_end, array_start, array_end, key, value + // Return false to discard the value + if (event == json::parse_event_t::key && parsed == json("password")) { + return false; // discard objects with "password" key + } + return true; +}); +``` + +## Serialization + +### To String + +```cpp +json j = {{"name", "Alice"}, {"age", 30}}; + +// Compact (no indentation) +std::string s = j.dump(); +// {"age":30,"name":"Alice"} + +// Pretty-printed (4-space indent) +std::string s = j.dump(4); +// { +// "age": 30, +// "name": "Alice" +// } + +// Custom indent character +std::string s = j.dump(1, '\t'); + +// Force ASCII output +std::string s = j.dump(-1, ' ', true); +// Non-ASCII chars are escaped as \uXXXX +``` + +### `dump()` Method Signature + +```cpp +string_t dump( + const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict +) const; +``` + +The `error_handler` controls how invalid UTF-8 in strings is handled: + +| Value | Behavior | +|---|---| +| `error_handler_t::strict` | Throw `type_error::316` | +| `error_handler_t::replace` | Replace invalid bytes with U+FFFD | +| `error_handler_t::ignore` | Skip invalid bytes | + +### To Stream + +```cpp +std::cout << j << std::endl; // compact +std::cout << std::setw(4) << j << "\n"; // pretty + +// To file +std::ofstream file("output.json"); +file << std::setw(4) << j; +``` + +## Type Inspection + +### Type Query Methods + +```cpp +json j = 42; + +j.type() // value_t::number_integer +j.type_name() // "number" + +j.is_null() // false +j.is_boolean() // false +j.is_number() // true +j.is_number_integer() // true +j.is_number_unsigned() // false +j.is_number_float() // false +j.is_object() // false +j.is_array() // false +j.is_string() // false +j.is_binary() // false +j.is_discarded() // false + +j.is_primitive() // true (null, string, boolean, number, binary) +j.is_structured() // false (object or array) +``` + +### Explicit Type Conversion + +```cpp +json j = 42; + +// Using get<T>() +int i = j.get<int>(); +double d = j.get<double>(); +std::string s = j.get<std::string>(); // throws type_error::302 + +// Using get_to() +int i; +j.get_to(i); + +// Using get_ref<T&>() +json j = "hello"; +const std::string& ref = j.get_ref<const std::string&>(); + +// Using get_ptr<T*>() +json j = "hello"; +const std::string* ptr = j.get_ptr<const std::string*>(); +if (ptr != nullptr) { + // use *ptr +} +``` + +### Implicit Type Conversion + +When `JSON_USE_IMPLICIT_CONVERSIONS` is enabled (default): + +```cpp +json j = 42; +int i = j; // implicit conversion + +json j = "hello"; +std::string s = j; // implicit conversion + +json j = {1, 2, 3}; +std::vector<int> v = j; // implicit conversion +``` + +### Cast to `value_t` + +```cpp +json j = 42; +json::value_t t = j; // implicit cast via operator value_t() +if (t == json::value_t::number_integer) { ... } +``` + +## Working with Objects + +### Creating and Modifying + +```cpp +json j; // null + +// operator[] implicitly converts null to object/array +j["name"] = "Alice"; // null → object, then insert +j["age"] = 30; +j["scores"] = {95, 87, 92}; + +// Nested objects +j["address"]["city"] = "Springfield"; +j["address"]["state"] = "IL"; +``` + +### Checking Keys + +```cpp +if (j.contains("name")) { ... } +if (j.count("name") > 0) { ... } +if (j.find("name") != j.end()) { ... } +``` + +### Removing Keys + +```cpp +j.erase("name"); // by key +j.erase(j.find("age")); // by iterator +``` + +### Getting with Default Value + +```cpp +std::string name = j.value("name", "unknown"); +int port = j.value("port", 8080); +``` + +## Working with Arrays + +### Creating and Modifying + +```cpp +json arr = json::array(); +arr.push_back(1); +arr.push_back("hello"); +arr += 3.14; // operator+= + +arr.emplace_back("world"); // in-place construction + +// Insert at position +arr.insert(arr.begin(), 0); +arr.insert(arr.begin() + 2, {10, 20}); +``` + +### Accessing Elements + +```cpp +int first = arr[0]; +int second = arr.at(1); // bounds-checked +int last = arr.back(); +int first2 = arr.front(); +``` + +### Modifying + +```cpp +arr.erase(arr.begin()); // remove first element +arr.erase(2); // remove element at index 2 +arr.clear(); // remove all elements +``` + +### Size and Capacity + +```cpp +arr.size(); // number of elements +arr.empty(); // true if no elements +arr.max_size(); // maximum possible elements +``` + +## Ordered JSON + +For insertion-order preservation: + +```cpp +nlohmann::ordered_json j; +j["z"] = 1; +j["a"] = 2; +j["m"] = 3; + +// Iteration preserves insertion order: z, a, m +for (auto& [key, val] : j.items()) { + std::cout << key << ": " << val << "\n"; +} +``` + +The `ordered_json` type uses `nlohmann::ordered_map` (a `std::vector`-based +map) instead of `std::map`. Lookups are O(n) instead of O(log n). + +## Copy and Comparison + +### Copying + +```cpp +json j1 = {{"key", "value"}}; +json j2 = j1; // deep copy +json j3(j1); // deep copy +json j4 = std::move(j1); // move (j1 becomes null) +``` + +### Comparison + +```cpp +json a = {1, 2, 3}; +json b = {1, 2, 3}; + +a == b; // true +a != b; // false +a < b; // false (same type, same value) + +// Cross-type numeric comparison +json(1) == json(1.0); // true +json(1) < json(1.5); // true +``` + +## Structured Bindings (C++17) + +```cpp +json j = {{"name", "Alice"}, {"age", 30}}; + +for (auto& [key, val] : j.items()) { + std::cout << key << " = " << val << "\n"; +} +``` + +## Common Patterns + +### Configuration File Loading + +```cpp +json load_config(const std::string& path) { + std::ifstream file(path); + if (!file.is_open()) { + return json::object(); + } + return json::parse(file, nullptr, true, true); // allow comments +} + +auto config = load_config("config.json"); +int port = config.value("port", 8080); +std::string host = config.value("host", "localhost"); +``` + +### Safe Value Extraction + +```cpp +template<typename T> +std::optional<T> safe_get(const json& j, const std::string& key) { + if (j.contains(key)) { + try { + return j.at(key).get<T>(); + } catch (const json::type_error&) { + return std::nullopt; + } + } + return std::nullopt; +} +``` + +### Building JSON Programmatically + +```cpp +json build_response(int status, const std::string& message) { + return { + {"status", status}, + {"message", message}, + {"timestamp", std::time(nullptr)}, + {"data", json::object()} + }; +} +``` + +### Merging Objects + +```cpp +json defaults = {{"color", "blue"}, {"size", 10}, {"visible", true}}; +json user_prefs = {{"color", "red"}, {"opacity", 0.8}}; + +defaults.update(user_prefs); +// defaults = {"color": "red", "size": 10, "visible": true, "opacity": 0.8} + +// Deep merge with merge_objects=true +defaults.update(user_prefs, true); +``` + +### Flattening and Unflattening + +```cpp +json nested = { + {"a", {{"b", {{"c", 42}}}}} +}; + +json flat = nested.flatten(); +// {"/a/b/c": 42} + +json restored = flat.unflatten(); +// {"a": {"b": {"c": 42}}} +``` + +## Error Handling Summary + +| Exception | When Thrown | +|---|---| +| `json::parse_error` | Invalid JSON input | +| `json::type_error` | Wrong type access (e.g., `string` on a number) | +| `json::out_of_range` | Index/key not found with `at()` | +| `json::invalid_iterator` | Invalid iterator operation | +| `json::other_error` | Miscellaneous errors | + +```cpp +try { + json j = json::parse("..."); + int val = j.at("missing_key").get<int>(); +} catch (json::parse_error& e) { + // e.id: 101, 102, 103, 104, 105 + // e.byte: position in input +} catch (json::out_of_range& e) { + // e.id: 401, 402, 403, 404, 405 +} catch (json::type_error& e) { + // e.id: 301, 302, 303, 304, 305, 306, 307, 308, ... +} +``` diff --git a/docs/handbook/json4cpp/binary-formats.md b/docs/handbook/json4cpp/binary-formats.md new file mode 100644 index 0000000000..9cb9f666f2 --- /dev/null +++ b/docs/handbook/json4cpp/binary-formats.md @@ -0,0 +1,411 @@ +# json4cpp — Binary Formats + +## Overview + +The library supports five binary serialization formats in addition to JSON +text. All are available as static methods on `basic_json`: + +| Format | To | From | RFC/Spec | +|---|---|---|---| +| CBOR | `to_cbor()` | `from_cbor()` | RFC 7049 | +| MessagePack | `to_msgpack()` | `from_msgpack()` | MessagePack spec | +| UBJSON | `to_ubjson()` | `from_ubjson()` | UBJSON spec | +| BSON | `to_bson()` | `from_bson()` | BSON spec | +| BJData | `to_bjdata()` | `from_bjdata()` | BJData spec | + +Binary serialization is useful for: +- Smaller payload sizes +- Faster parsing +- Native binary data support +- Type-rich encodings (timestamps, binary subtypes) + +## CBOR (Concise Binary Object Representation) + +### Serialization + +```cpp +// To vector<uint8_t> +static std::vector<std::uint8_t> to_cbor(const basic_json& j); + +// To output adapter (stream, string, vector) +static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o); +static void to_cbor(const basic_json& j, detail::output_adapter<char> o); +``` + +```cpp +json j = {{"compact", true}, {"schema", 0}}; + +// Serialize to byte vector +auto cbor = json::to_cbor(j); + +// Serialize to stream +std::ofstream out("data.cbor", std::ios::binary); +json::to_cbor(j, out); +``` + +### Deserialization + +```cpp +template<typename InputType> +static basic_json from_cbor(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); +``` + +Parameters: +- `strict` — if `true`, requires that all bytes are consumed +- `allow_exceptions` — if `false`, returns discarded value on error +- `tag_handler` — how to handle CBOR tags + +```cpp +auto j = json::from_cbor(cbor); +``` + +### CBOR Tag Handling + +```cpp +enum class cbor_tag_handler_t +{ + error, ///< throw parse_error on any tag + ignore, ///< ignore tags + store ///< store tags as binary subtype +}; +``` + +```cpp +// Ignore CBOR tags +auto j = json::from_cbor(data, true, true, json::cbor_tag_handler_t::ignore); + +// Store CBOR tags as subtypes in binary values +auto j = json::from_cbor(data, true, true, json::cbor_tag_handler_t::store); +``` + +### CBOR Type Mapping + +| JSON Type | CBOR Type | +|---|---| +| null | null (0xF6) | +| boolean | true/false (0xF5/0xF4) | +| number_integer | negative/unsigned integer | +| number_unsigned | unsigned integer | +| number_float | IEEE 754 double (0xFB) or half-precision (0xF9) | +| string | text string (major type 3) | +| array | array (major type 4) | +| object | map (major type 5) | +| binary | byte string (major type 2) | + +## MessagePack + +### Serialization + +```cpp +static std::vector<std::uint8_t> to_msgpack(const basic_json& j); +static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o); +static void to_msgpack(const basic_json& j, detail::output_adapter<char> o); +``` + +```cpp +json j = {{"array", {1, 2, 3}}, {"null", nullptr}}; +auto msgpack = json::to_msgpack(j); +``` + +### Deserialization + +```cpp +template<typename InputType> +static basic_json from_msgpack(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true); +``` + +```cpp +auto j = json::from_msgpack(msgpack); +``` + +### MessagePack Type Mapping + +| JSON Type | MessagePack Type | +|---|---| +| null | nil (0xC0) | +| boolean | true/false (0xC3/0xC2) | +| number_integer | int 8/16/32/64 or negative fixint | +| number_unsigned | uint 8/16/32/64 or positive fixint | +| number_float | float 32 or float 64 | +| string | fixstr / str 8/16/32 | +| array | fixarray / array 16/32 | +| object | fixmap / map 16/32 | +| binary | bin 8/16/32 | +| binary with subtype | ext 8/16/32 / fixext 1/2/4/8/16 | + +The library chooses the **smallest** encoding that fits the value. + +### Ext Types + +MessagePack extension types carry a type byte. The library maps this to the +binary subtype: + +```cpp +json j = json::binary({0x01, 0x02, 0x03}, 42); // subtype 42 +auto mp = json::to_msgpack(j); +// Encoded as ext with type byte 42 + +auto j2 = json::from_msgpack(mp); +assert(j2.get_binary().subtype() == 42); +``` + +## UBJSON (Universal Binary JSON) + +### Serialization + +```cpp +static std::vector<std::uint8_t> to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false); +``` + +Parameters: +- `use_size` — write container size markers (enables optimized containers) +- `use_type` — write type markers for homogeneous containers (requires `use_size`) + +```cpp +json j = {1, 2, 3, 4, 5}; + +// Without optimization +auto ub1 = json::to_ubjson(j); + +// With size optimization +auto ub2 = json::to_ubjson(j, true); + +// With size+type optimization (smallest for homogeneous arrays) +auto ub3 = json::to_ubjson(j, true, true); +``` + +### Deserialization + +```cpp +template<typename InputType> +static basic_json from_ubjson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true); +``` + +### UBJSON Type Markers + +| Marker | Type | +|---|---| +| `Z` | null | +| `T` / `F` | true / false | +| `i` | int8 | +| `U` | uint8 | +| `I` | int16 | +| `l` | int32 | +| `L` | int64 | +| `d` | float32 | +| `D` | float64 | +| `C` | char | +| `S` | string | +| `[` / `]` | array begin / end | +| `{` / `}` | object begin / end | +| `H` | high-precision number (string representation) | + +## BSON (Binary JSON) + +### Serialization + +```cpp +static std::vector<std::uint8_t> to_bson(const basic_json& j); +static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o); +static void to_bson(const basic_json& j, detail::output_adapter<char> o); +``` + +**Important:** BSON requires the top-level value to be an **object**: + +```cpp +json j = {{"key", "value"}, {"num", 42}}; +auto bson = json::to_bson(j); + +// json j = {1, 2, 3}; +// json::to_bson(j); // throws type_error::317 — not an object +``` + +### Deserialization + +```cpp +template<typename InputType> +static basic_json from_bson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true); +``` + +### BSON Type Mapping + +| JSON Type | BSON Type | +|---|---| +| null | 0x0A (Null) | +| boolean | 0x08 (Boolean) | +| number_integer | 0x10 (int32) or 0x12 (int64) | +| number_unsigned | 0x10 or 0x12 (depends on value) | +| number_float | 0x01 (double) | +| string | 0x02 (String) | +| array | 0x04 (Array) — encoded as object with "0", "1", ... keys | +| object | 0x03 (Document) | +| binary | 0x05 (Binary) | + +### BSON Binary Subtypes + +```cpp +json j; +j["data"] = json::binary({0x01, 0x02}, 0x80); // subtype 0x80 +auto bson = json::to_bson(j); +// Binary encoded with subtype byte 0x80 +``` + +## BJData (Binary JData) + +BJData extends UBJSON with additional types for N-dimensional arrays and +optimized integer types. + +### Serialization + +```cpp +static std::vector<std::uint8_t> to_bjdata(const basic_json& j, + const bool use_size = false, + const bool use_type = false); +``` + +### Deserialization + +```cpp +template<typename InputType> +static basic_json from_bjdata(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true); +``` + +### Additional BJData Types + +Beyond UBJSON types, BJData adds: + +| Marker | Type | +|---|---| +| `u` | uint16 | +| `m` | uint32 | +| `M` | uint64 | +| `h` | float16 (half-precision) | + +## Roundtrip Between Formats + +Binary formats can preserve the same data as JSON text, but with some +differences: + +```cpp +json original = { + {"name", "test"}, + {"values", {1, 2, 3}}, + {"data", json::binary({0xFF, 0xFE})} +}; + +// JSON text cannot represent binary +std::string text = original.dump(); +// "data" field would cause issues in text form + +// Binary formats can represent binary natively +auto cbor = json::to_cbor(original); +auto restored = json::from_cbor(cbor); +assert(original == restored); // exact roundtrip + +// Cross-format conversion +auto mp = json::to_msgpack(original); +auto from_mp = json::from_msgpack(mp); +assert(original == from_mp); +``` + +## Size Comparison + +Typical size savings over JSON text: + +| Data | JSON | CBOR | MessagePack | UBJSON | BSON | +|---|---|---|---|---|---| +| `{"a":1}` | 7 bytes | 5 bytes | 4 bytes | 6 bytes | 18 bytes | +| `[1,2,3]` | 7 bytes | 4 bytes | 4 bytes | 6 bytes | N/A (top-level array) | +| `true` | 4 bytes | 1 byte | 1 byte | 1 byte | N/A (top-level bool) | + +BSON has the most overhead due to its document-structure requirements. +MessagePack and CBOR are generally the most compact. + +## Stream-Based Serialization + +All binary formats support streaming to/from `std::ostream` / `std::istream`: + +```cpp +// Write CBOR to file +std::ofstream out("data.cbor", std::ios::binary); +json::to_cbor(j, out); + +// Read CBOR from file +std::ifstream in("data.cbor", std::ios::binary); +json j = json::from_cbor(in); +``` + +## Strict vs. Non-Strict Parsing + +All `from_*` functions accept a `strict` parameter: + +- `strict = true` (default): all input bytes must be consumed. Extra + trailing data causes a parse error. +- `strict = false`: parsing stops after the first valid value. Remaining + input is ignored. + +```cpp +std::vector<uint8_t> data = /* two CBOR values concatenated */; + +// Strict: fails because of trailing data +// json::from_cbor(data, true); + +// Non-strict: parses only the first value +json j = json::from_cbor(data, false); +``` + +## Binary Reader / Writer Architecture + +The binary I/O is implemented by two internal classes: + +### `binary_reader` + +Located in `include/nlohmann/detail/input/binary_reader.hpp`: + +```cpp +template<typename BasicJsonType, typename InputAdapterType, typename SAX> +class binary_reader +{ + bool parse_cbor_internal(bool get_char, int tag_handler); + bool parse_msgpack_internal(); + bool parse_ubjson_internal(bool get_char = true); + bool parse_bson_internal(); + bool parse_bjdata_internal(); + // ... +}; +``` + +Uses the SAX interface internally — each decoded value is reported to a +SAX handler (typically `json_sax_dom_parser`) which builds the JSON tree. + +### `binary_writer` + +Located in `include/nlohmann/detail/output/binary_writer.hpp`: + +```cpp +template<typename BasicJsonType, typename CharType> +class binary_writer +{ + void write_cbor(const BasicJsonType& j); + void write_msgpack(const BasicJsonType& j); + void write_ubjson(const BasicJsonType& j, ...); + void write_bson(const BasicJsonType& j); + void write_bjdata(const BasicJsonType& j, ...); + // ... +}; +``` + +Directly writes encoded bytes to an `output_adapter`. diff --git a/docs/handbook/json4cpp/building.md b/docs/handbook/json4cpp/building.md new file mode 100644 index 0000000000..73e29a65fc --- /dev/null +++ b/docs/handbook/json4cpp/building.md @@ -0,0 +1,430 @@ +# json4cpp — Building and Integration + +## Header-Only Usage + +json4cpp (nlohmann/json 3.12.0) is a header-only library. The simplest way +to use it is to copy the single amalgamated header and include it: + +```cpp +#include "nlohmann/json.hpp" + +using json = nlohmann::json; +``` + +### Single Header vs. Multi-Header + +The library ships in two forms: + +| Form | Location | Use Case | +|---|---|---| +| Single header | `single_include/nlohmann/json.hpp` | Simplest integration | +| Multi-header | `include/nlohmann/json.hpp` + `include/nlohmann/detail/` | Better IDE navigation, faster incremental builds | + +The single header (`json.hpp`, ~25,000 lines) is generated by amalgamating +all the multi-header files. It also ships `json_fwd.hpp` for forward +declarations without pulling in the full implementation. + +### Forward Declaration Header + +```cpp +#include <nlohmann/json_fwd.hpp> + +// Now you can declare functions accepting json parameters +void process(const nlohmann::json& data); +``` + +The forward header declares `basic_json`, `json`, `ordered_json`, +`json_pointer`, `ordered_map`, and `adl_serializer` without including +any implementation. + +## CMake Integration + +### As a Subdirectory + +```cmake +add_subdirectory(json4cpp) # or wherever the library lives + +target_link_libraries(my_target PRIVATE nlohmann_json::nlohmann_json) +``` + +### Via `FetchContent` + +```cmake +include(FetchContent) +FetchContent_Declare( + json + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/json4cpp +) +FetchContent_MakeAvailable(json) + +target_link_libraries(my_target PRIVATE nlohmann_json::nlohmann_json) +``` + +### Via `find_package` (After Install) + +```cmake +find_package(nlohmann_json 3.12.0 REQUIRED) +target_link_libraries(my_target PRIVATE nlohmann_json::nlohmann_json) +``` + +### Target Include Directories + +For the simplest possible integration without CMake targets: + +```cmake +target_include_directories(my_target PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/json4cpp/single_include +) +``` + +Or for multi-header: + +```cmake +target_include_directories(my_target PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/json4cpp/include +) +``` + +## CMake Options Reference + +The top-level `CMakeLists.txt` defines these options: + +```cmake +cmake_minimum_required(VERSION 3.5...4.0) +project(nlohmann_json VERSION 3.12.0 LANGUAGES CXX) +``` + +### Build Options + +| Option | Default | Description | +|---|---|---| +| `JSON_BuildTests` | `ON` (main project) | Build the test suite | +| `JSON_CI` | `OFF` | Enable CI build targets | +| `JSON_Diagnostics` | `OFF` | Extended diagnostic messages | +| `JSON_Diagnostic_Positions` | `OFF` | Track byte positions | +| `JSON_GlobalUDLs` | `ON` | Place UDLs in global namespace | +| `JSON_ImplicitConversions` | `ON` | Enable implicit `operator T()` | +| `JSON_DisableEnumSerialization` | `OFF` | Disable automatic enum conversion | +| `JSON_LegacyDiscardedValueComparison` | `OFF` | Legacy comparison behavior | +| `JSON_Install` | `ON` (main project) | Install CMake targets | +| `JSON_MultipleHeaders` | `ON` | Use multi-header tree | +| `JSON_SystemInclude` | `OFF` | Include as system headers | + +### Configuration Variables + +```cmake +NLOHMANN_JSON_TARGET_NAME # Override target name (default: nlohmann_json) +NLOHMANN_JSON_CONFIG_INSTALL_DIR # CMake config install dir +NLOHMANN_JSON_INCLUDE_INSTALL_DIR # Header install dir +``` + +### Header Selection Logic + +```cmake +if (JSON_MultipleHeaders) + set(NLOHMANN_JSON_INCLUDE_BUILD_DIR "${PROJECT_SOURCE_DIR}/include/") +else() + set(NLOHMANN_JSON_INCLUDE_BUILD_DIR "${PROJECT_SOURCE_DIR}/single_include/") +endif() +``` + +### Compile Definitions Set by CMake + +When options are toggled, CMake sets preprocessor definitions on the target: + +```cmake +if (JSON_Diagnostics) + target_compile_definitions(nlohmann_json INTERFACE JSON_DIAGNOSTICS=1) +endif() + +if (NOT JSON_ImplicitConversions) + target_compile_definitions(nlohmann_json INTERFACE JSON_USE_IMPLICIT_CONVERSIONS=0) +endif() + +if (JSON_DisableEnumSerialization) + target_compile_definitions(nlohmann_json INTERFACE JSON_DISABLE_ENUM_SERIALIZATION=1) +endif() + +if (JSON_Diagnostic_Positions) + target_compile_definitions(nlohmann_json INTERFACE JSON_DIAGNOSTIC_POSITIONS=1) +endif() + +if (NOT JSON_GlobalUDLs) + target_compile_definitions(nlohmann_json INTERFACE JSON_USE_GLOBAL_UDLS=0) +endif() + +if (JSON_LegacyDiscardedValueComparison) + target_compile_definitions(nlohmann_json INTERFACE JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1) +endif() +``` + +## Other Build Systems + +### Bazel + +```python +# BUILD.bazel is provided at the top level +cc_library( + name = "json", + hdrs = glob(["include/**/*.hpp"]), + includes = ["include"], +) +``` + +A `MODULE.bazel` file is also provided for Bzlmod support. + +### Meson + +```meson +# meson.build is provided at the top level +nlohmann_json_dep = dependency('nlohmann_json', fallback: ['nlohmann_json', 'nlohmann_json_dep']) +``` + +### Swift Package Manager + +```swift +// Package.swift is provided +.package(path: "json4cpp") +``` + +### pkg-config + +After installation, a `nlohmann_json.pc` file is generated from +`cmake/pkg-config.pc.in`: + +``` +pkg-config --cflags nlohmann_json +``` + +## Preprocessor Configuration Macros + +These macros can be defined before including the header or via compiler +flags to control library behavior: + +### Core Behavior + +| Macro | Values | Effect | +|---|---|---| +| `JSON_DIAGNOSTICS` | `0`/`1` | Extended error messages with parent-chain paths | +| `JSON_DIAGNOSTIC_POSITIONS` | `0`/`1` | Track byte positions in parsed values | +| `JSON_USE_IMPLICIT_CONVERSIONS` | `0`/`1` | Enable/disable implicit `operator T()` | +| `JSON_DISABLE_ENUM_SERIALIZATION` | `0`/`1` | Disable enum-to-integer serialization | +| `JSON_USE_GLOBAL_UDLS` | `0`/`1` | Place `_json` / `_json_pointer` UDLs in global scope | +| `JSON_NO_IO` | defined/undefined | Disable all stream-based I/O | +| `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON` | `0`/`1` | Legacy discarded value comparison | + +### Assertion Customization + +```cpp +// Override the assertion macro (default: assert()) +#define JSON_ASSERT(x) my_assert(x) +``` + +### Exception Customization + +```cpp +// Override throw behavior +#define JSON_THROW(exception) throw exception +#define JSON_TRY try +#define JSON_CATCH(exception) catch(exception) +#define JSON_INTERNAL_CATCH(exception) catch(exception) +``` + +To disable exceptions entirely: + +```cpp +#define JSON_THROW(exception) std::abort() +#define JSON_TRY if(true) +#define JSON_CATCH(exception) if(false) +#define JSON_INTERNAL_CATCH(exception) if(false) +``` + +### Version Macros + +```cpp +NLOHMANN_JSON_VERSION_MAJOR // 3 +NLOHMANN_JSON_VERSION_MINOR // 12 +NLOHMANN_JSON_VERSION_PATCH // 0 +``` + +### Standard Detection Macros + +Set automatically based on the compiler: + +```cpp +JSON_HAS_CPP_11 // always 1 +JSON_HAS_CPP_14 // 1 if C++14 or higher +JSON_HAS_CPP_17 // 1 if C++17 or higher +JSON_HAS_CPP_20 // 1 if C++20 or higher +``` + +### RTTI Detection + +```cpp +JSON_HAS_STATIC_RTTI // 1 if RTTI is available +``` + +### Three-Way Comparison Detection + +```cpp +JSON_HAS_THREE_WAY_COMPARISON // 1 if <=> is available +``` + +## C++20 Module Support + +The library includes experimental C++20 module support: + +```cmake +option(NLOHMANN_JSON_BUILD_MODULES "Build C++ modules support" OFF) +``` + +When enabled and CMake >= 3.28 is available, the module is built from +`src/modules/`. Usage: + +```cpp +import nlohmann.json; +``` + +## Compiler-Specific Notes + +### GCC + +The `cmake/gcc_flags.cmake` file configures GCC-specific warning flags. +GCC 4.8 support requires workarounds (user-defined literal spacing). + +### Clang + +`cmake/clang_flags.cmake` handles Clang warning configuration. The +`-Wweak-vtables` warning is suppressed in `detail/exceptions.hpp` +since header-only libraries cannot have out-of-line vtables. + +### MSVC + +MSVC receives specific warning suppressions. The `nlohmann_json.natvis` +file provides Visual Studio debugger visualization: + +```xml +<!-- nlohmann_json.natvis provides structured views in the VS debugger --> +``` + +## Installation + +### Default Installation Layout + +```bash +cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local +cmake --build build +cmake --install build +``` + +This installs: + +``` +/usr/local/include/nlohmann/ # Headers +/usr/local/share/cmake/nlohmann_json/ # CMake config files +/usr/local/share/pkgconfig/ # pkg-config file +``` + +### Controlling Installation + +```cmake +set(JSON_Install OFF) # Disable installation entirely +``` + +### Version Compatibility + +The installed `nlohmann_jsonConfigVersion.cmake` file supports version +range checking, allowing consumers to request minimum versions: + +```cmake +find_package(nlohmann_json 3.11.0 REQUIRED) # any 3.x >= 3.11.0 +``` + +## Integration Patterns + +### Pattern 1: Copy Single Header + +```bash +cp json4cpp/single_include/nlohmann/json.hpp my_project/third_party/ +``` + +```cpp +#include "third_party/json.hpp" +``` + +### Pattern 2: Git Submodule + CMake + +```bash +git submodule add <url> third_party/json +``` + +```cmake +add_subdirectory(third_party/json) +target_link_libraries(my_target PRIVATE nlohmann_json::nlohmann_json) +``` + +### Pattern 3: System Package + +Most Linux distributions package nlohmann/json: + +```bash +# Debian/Ubuntu +apt install nlohmann-json3-dev + +# Fedora +dnf install json-devel + +# Arch +pacman -S nlohmann-json + +# macOS +brew install nlohmann-json +``` + +### Pattern 4: Header-Only with Forward Declarations + +For faster compilation, use the forward declaration header in headers +and the full header only in implementation files: + +```cpp +// my_class.hpp +#include <nlohmann/json_fwd.hpp> +class MyClass { + void process(const nlohmann::json& j); +}; + +// my_class.cpp +#include <nlohmann/json.hpp> +#include "my_class.hpp" +void MyClass::process(const nlohmann::json& j) { ... } +``` + +## Compilation Speed Tips + +1. **Use `json_fwd.hpp`** in headers to avoid pulling the full + implementation into every translation unit. + +2. **Precompiled headers** — add `nlohmann/json.hpp` to your PCH: + ```cmake + target_precompile_headers(my_target PRIVATE <nlohmann/json.hpp>) + ``` + +3. **Unity builds** work naturally since the library is header-only. + +4. **Multi-header mode** with `JSON_MultipleHeaders=ON` can improve + incremental rebuild times since changes to one detail header don't + invalidate the entire amalgamated file. + +5. **`JSON_NO_IO`** — define this if you don't need stream operators, + reducing the include chain. + +## Minimum Requirements + +| Requirement | Minimum | +|---|---| +| C++ Standard | C++11 | +| CMake | 3.5 (3.28 for modules) | +| GCC | 4.8 | +| Clang | 3.4 | +| MSVC | 2015 (19.0) | +| Intel C++ | 2017 | diff --git a/docs/handbook/json4cpp/code-style.md b/docs/handbook/json4cpp/code-style.md new file mode 100644 index 0000000000..05fb76f4dd --- /dev/null +++ b/docs/handbook/json4cpp/code-style.md @@ -0,0 +1,209 @@ +# json4cpp — Code Style & Conventions + +## Source Organisation + +### Directory Layout + +``` +json4cpp/ +├── include/nlohmann/ # Multi-header installation +│ ├── json.hpp # Main header (includes everything) +│ ├── json_fwd.hpp # Forward declarations only +│ ├── adl_serializer.hpp # ADL-based serializer +│ ├── byte_container_with_subtype.hpp +│ ├── ordered_map.hpp # Insertion-order map +│ └── detail/ # Internal implementation +│ ├── exceptions.hpp # Exception hierarchy +│ ├── hash.hpp # std::hash specialization +│ ├── json_pointer.hpp # RFC 6901 implementation +│ ├── json_ref.hpp # Internal reference wrapper +│ ├── macro_scope.hpp # Macro definitions +│ ├── macro_unscope.hpp # Macro undefinitions +│ ├── string_concat.hpp # String concatenation helper +│ ├── string_escape.hpp # String escaping utilities +│ ├── value_t.hpp # value_t enum +│ ├── abi_macros.hpp # ABI versioning macros +│ ├── conversions/ # Type conversion traits +│ ├── input/ # Parsing pipeline +│ ├── iterators/ # Iterator implementations +│ ├── meta/ # Type traits & SFINAE +│ └── output/ # Serialization pipeline +├── single_include/nlohmann/ # Single-header (amalgamated) +│ └── json.hpp # Complete library in one file +├── tests/ # Test suite (doctest) +│ ├── CMakeLists.txt +│ └── src/ +│ └── unit-*.cpp # One file per feature area +└── CMakeLists.txt # Build configuration +``` + +### Public vs. Internal API + +- `include/nlohmann/*.hpp` — public API, included by users +- `include/nlohmann/detail/` — internal, not for direct inclusion +- `single_include/` — generated amalgamation, mirrors the public API + +Users should only include `<nlohmann/json.hpp>` or +`<nlohmann/json_fwd.hpp>`. + +## Naming Conventions + +### Types + +- Template parameters: `PascalCase` — `BasicJsonType`, `ObjectType`, + `InputAdapterType` +- Type aliases: `snake_case` — `value_t`, `object_t`, `string_t`, + `number_integer_t` +- Internal classes: `snake_case` — `iter_impl`, `binary_reader`, + `json_sax_dom_parser` + +### Functions and Methods + +- All functions: `snake_case` — `parse()`, `dump()`, `push_back()`, + `is_null()`, `get_to()`, `merge_patch()` +- Private methods: `snake_case` — `set_parent()`, `assert_invariant()` + +### Variables + +- Member variables: `m_` prefix — `m_type`, `m_value`, `m_parent` +- Local variables: `snake_case` — `reference_tokens`, `token_buffer` + +### Macros + +- All macros: `SCREAMING_SNAKE_CASE` with project prefix +- Public macros: `NLOHMANN_` prefix or `JSON_` prefix + - `NLOHMANN_DEFINE_TYPE_INTRUSIVE` + - `NLOHMANN_JSON_SERIALIZE_ENUM` + - `JSON_DIAGNOSTICS` + - `JSON_USE_IMPLICIT_CONVERSIONS` +- Internal macros: `NLOHMANN_JSON_` prefix for implementation detail macros +- All macros are undefined by `macro_unscope.hpp` to avoid pollution + +### Namespaces + +```cpp +namespace nlohmann { + // Public API: basic_json, json, ordered_json, json_pointer, ... + namespace detail { + // Internal implementation + } + namespace literals { + namespace json_literals { + // _json, _json_pointer UDLs + } + } +} +``` + +The `NLOHMANN_JSON_NAMESPACE_BEGIN` / `NLOHMANN_JSON_NAMESPACE_END` macros +handle optional ABI versioning via inline namespaces. + +## Template Style + +### SFINAE Guards + +The library uses SFINAE extensively to constrain overloads: + +```cpp +template<typename BasicJsonType, typename T, + enable_if_t<is_compatible_type<BasicJsonType, T>::value, int> = 0> +void to_json(BasicJsonType& j, T&& val); +``` + +The `enable_if_t<..., int> = 0` pattern is used throughout instead of +`enable_if_t<..., void>` or return-type SFINAE. + +### Tag Dispatch + +Priority tags resolve overload ambiguity: + +```cpp +template<unsigned N> struct priority_tag : priority_tag<N - 1> {}; +template<> struct priority_tag<0> {}; +``` + +Higher-numbered tags are tried first (since they inherit from lower ones). + +### `static_assert` Guards + +Critical type requirements use `static_assert` with readable messages: + +```cpp +static_assert(std::is_default_constructible<T>::value, + "T must be default constructible"); +``` + +## Header Guards + +Each header uses `#ifndef` guards following the pattern: + +```cpp +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ +// ... +#endif // INCLUDE_NLOHMANN_JSON_HPP_ +``` + +Detail headers follow `INCLUDE_NLOHMANN_JSON_DETAIL_*` naming. + +## Code Documentation + +### Doxygen-Style Comments + +Public API methods use `///` or `/** */` with standard Doxygen tags: + +```cpp +/// @brief parse a JSON value from a string +/// @param[in] i the input to parse +/// @param[in] cb a callback function (default: none) +/// @param[in] allow_exceptions whether exceptions should be thrown +/// @return the parsed JSON value +static basic_json parse(InputType&& i, ...); +``` + +### `@sa` Cross References + +Related methods are linked with `@sa`: + +```cpp +/// @sa dump() for serialization +/// @sa operator>> for stream parsing +``` + +### `@throw` Documentation + +Exception-throwing methods document which exceptions they throw: + +```cpp +/// @throw parse_error.101 if unexpected token +/// @throw parse_error.102 if invalid unicode escape +``` + +## Error Handling Style + +- Public API methods that can fail throw typed exceptions from the + hierarchy (`parse_error`, `type_error`, `out_of_range`, + `invalid_iterator`, `other_error`) +- Each exception has a unique numeric ID for programmatic handling +- Error messages follow the format: + `[json.exception.<type>.<id>] <description>` +- Internal assertions use `JSON_ASSERT(condition)` which maps to + `assert()` by default + +## Compatibility + +### C++ Standard + +- Minimum: C++11 +- Optional features with C++14: heterogeneous lookup + (`std::less<>`) +- Optional features with C++17: `std::string_view`, `std::optional`, + `std::variant`, `std::filesystem::path`, structured bindings, + `if constexpr` +- Optional features with C++20: modules, `operator<=>` + +### Compiler Notes + +Tested compilers include GCC ≥ 4.8, Clang ≥ 3.4, MSVC ≥ 2015, Intel +C++, and various others. Compiler-specific workarounds are guarded with +preprocessor conditionals. diff --git a/docs/handbook/json4cpp/custom-types.md b/docs/handbook/json4cpp/custom-types.md new file mode 100644 index 0000000000..086fa0ebcc --- /dev/null +++ b/docs/handbook/json4cpp/custom-types.md @@ -0,0 +1,465 @@ +# json4cpp — Custom Type Serialization + +## ADL-Based Serialization + +The library uses **Argument-Dependent Lookup** (ADL) to find `to_json()` +and `from_json()` free functions for user-defined types. This allows +seamless conversion without modifying the library. + +### Basic Pattern + +Define `to_json()` and `from_json()` as free functions in the **same +namespace** as your type: + +```cpp +namespace myapp { + +struct Person { + std::string name; + int age; +}; + +void to_json(nlohmann::json& j, const Person& p) { + j = nlohmann::json{{"name", p.name}, {"age", p.age}}; +} + +void from_json(const nlohmann::json& j, Person& p) { + j.at("name").get_to(p.name); + j.at("age").get_to(p.age); +} + +} // namespace myapp +``` + +Usage: + +```cpp +myapp::Person alice{"alice", 30}; + +// Serialization +json j = alice; // calls myapp::to_json via ADL +// or +json j2; +j2 = alice; + +// Deserialization +auto bob = j.get<myapp::Person>(); // calls myapp::from_json via ADL +// or +myapp::Person carol; +j.get_to(carol); +``` + +### How ADL Resolution Works + +When you write `json j = my_obj;`, the library calls: + +```cpp +nlohmann::adl_serializer<MyType>::to_json(j, my_obj); +``` + +The default `adl_serializer` implementation delegates via an unqualified +call: + +```cpp +template<typename BasicJsonType, typename TargetType> +static auto to_json(BasicJsonType& j, TargetType&& val) + -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void()) +{ + ::nlohmann::to_json(j, std::forward<TargetType>(val)); +} +``` + +The unqualified call to `::nlohmann::to_json(j, val)` finds: +1. Built-in overloads in `namespace nlohmann` (via `using` declarations) +2. User-provided overloads in the type's namespace (via ADL) + +## `get_to()` Helper + +```cpp +template<typename ValueType> +ValueType& get_to(ValueType& v) const; +``` + +Converts and writes into an existing variable: + +```cpp +json j = {{"x", 1}, {"y", 2}}; +int x, y; +j.at("x").get_to(x); +j.at("y").get_to(y); +``` + +## Automatic Macros + +The library provides macros to auto-generate `to_json()` and `from_json()` +without writing them manually. All macros are defined in +`include/nlohmann/detail/macro_scope.hpp`. + +### `NLOHMANN_DEFINE_TYPE_INTRUSIVE` + +Defines `to_json()` and `from_json()` as **friend functions** inside the +class body. Requires all fields to be present during deserialization: + +```cpp +struct Point { + double x; + double y; + double z; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(Point, x, y, z) +}; +``` + +This expands to: + +```cpp +friend void to_json(nlohmann::json& nlohmann_json_j, const Point& nlohmann_json_t) { + nlohmann_json_j["x"] = nlohmann_json_t.x; + nlohmann_json_j["y"] = nlohmann_json_t.y; + nlohmann_json_j["z"] = nlohmann_json_t.z; +} + +friend void from_json(const nlohmann::json& nlohmann_json_j, Point& nlohmann_json_t) { + nlohmann_json_j.at("x").get_to(nlohmann_json_t.x); + nlohmann_json_j.at("y").get_to(nlohmann_json_t.y); + nlohmann_json_j.at("z").get_to(nlohmann_json_t.z); +} +``` + +### `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` + +Same as above, but uses `value()` instead of `at()` during deserialization. +Missing keys get the default-constructed or current value instead of +throwing: + +```cpp +struct Config { + std::string host = "localhost"; + int port = 8080; + bool debug = false; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Config, host, port, debug) +}; +``` + +Now parsing `{}` produces a Config with all default values instead of +throwing. + +### `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE` + +Generates only the `to_json()` function (no `from_json()`). Useful for +types that should be serializable but not deserializable: + +```cpp +struct LogEntry { + std::string timestamp; + std::string message; + int level; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(LogEntry, timestamp, message, level) +}; +``` + +### `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` + +Defines `to_json()` and `from_json()` as **free functions** outside the +class. Requires all members to be public: + +```cpp +struct Color { + int r, g, b; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Color, r, g, b) +``` + +### `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` + +Non-intrusive version with default values for missing keys: + +```cpp +struct Margin { + int top = 0; + int right = 0; + int bottom = 0; + int left = 0; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Margin, top, right, bottom, left) +``` + +### `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE` + +Non-intrusive, serialize-only: + +```cpp +struct Metric { + std::string name; + double value; +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Metric, name, value) +``` + +## Derived Type Macros + +For inheritance hierarchies, use the `DERIVED_TYPE` variants. These include +the base class fields: + +### `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE` + +```cpp +struct Base { + std::string id; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(Base, id) +}; + +struct Derived : Base { + int value; + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Derived, Base, value) +}; +``` + +This generates serialization that includes both `id` (from Base) and +`value` (from Derived). + +### All Derived Variants + +| Macro | Intrusive | Default | Serialize-Only | +|---|---|---|---| +| `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE` | Yes | No | No | +| `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT` | Yes | Yes | No | +| `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE` | Yes | — | Yes | +| `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE` | No | No | No | +| `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT` | No | Yes | No | +| `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE` | No | — | Yes | + +## Low-Level Macros + +### `NLOHMANN_JSON_TO` / `NLOHMANN_JSON_FROM` + +Building-block macros for custom serialization: + +```cpp +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) \ + nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); +``` + +These are used internally by the `NLOHMANN_DEFINE_TYPE_*` macros and can +be used directly for custom patterns. + +## Custom `adl_serializer` Specialization + +For types where you can't add free functions (e.g., third-party types), +specialize `adl_serializer`: + +```cpp +namespace nlohmann { + +template<> +struct adl_serializer<third_party::Point3D> { + static void to_json(json& j, const third_party::Point3D& p) { + j = json{{"x", p.x()}, {"y", p.y()}, {"z", p.z()}}; + } + + static void from_json(const json& j, third_party::Point3D& p) { + p = third_party::Point3D( + j.at("x").get<double>(), + j.at("y").get<double>(), + j.at("z").get<double>() + ); + } +}; + +} // namespace nlohmann +``` + +### Non-Default-Constructible Types + +For types without a default constructor, implement `from_json()` as a +static method returning the constructed value: + +```cpp +namespace nlohmann { + +template<> +struct adl_serializer<Immutable> { + static Immutable from_json(const json& j) { + return Immutable(j.at("x").get<int>(), j.at("y").get<int>()); + } + + static void to_json(json& j, const Immutable& val) { + j = json{{"x", val.x()}, {"y", val.y()}}; + } +}; + +} // namespace nlohmann +``` + +Usage: + +```cpp +json j = {{"x", 1}, {"y", 2}}; +auto val = j.get<Immutable>(); // calls adl_serializer<Immutable>::from_json(j) +``` + +## Enum Serialization + +### Default: Integer Mapping + +By default, enums are serialized as their underlying integer value: + +```cpp +enum class Status { active, inactive, pending }; + +json j = Status::active; // 0 +auto s = j.get<Status>(); // Status::active +``` + +### Disabling Enum Serialization + +```cpp +#define JSON_DISABLE_ENUM_SERIALIZATION 1 +``` + +Or via CMake: + +```cmake +set(JSON_DisableEnumSerialization ON) +``` + +### Custom Enum Mapping with `NLOHMANN_JSON_SERIALIZE_ENUM` + +```cpp +enum class Color { red, green, blue }; + +NLOHMANN_JSON_SERIALIZE_ENUM(Color, { + {Color::red, "red"}, + {Color::green, "green"}, + {Color::blue, "blue"}, +}) +``` + +This generates both `to_json()` and `from_json()` that map between enum +values and strings: + +```cpp +json j = Color::red; // "red" +auto c = j.get<Color>(); // Color::red + +json j2 = "unknown"; +auto c2 = j2.get<Color>(); // Color::red (first entry is the default) +``` + +The first entry in the mapping serves as the default for unrecognized +values during deserialization. + +## Nested Types + +```cpp +struct Address { + std::string city; + std::string zip; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(Address, city, zip) +}; + +struct Employee { + std::string name; + Address address; + std::vector<std::string> skills; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(Employee, name, address, skills) +}; +``` + +The library handles nesting automatically — `Address` has its own +serialization, and `Employee` uses it implicitly: + +```cpp +Employee emp{"alice", {"wonderland", "12345"}, {"c++", "python"}}; +json j = emp; +// { +// "name": "alice", +// "address": {"city": "wonderland", "zip": "12345"}, +// "skills": ["c++", "python"] +// } + +auto emp2 = j.get<Employee>(); +``` + +## Optional Fields + +Use `std::optional` (C++17) for truly optional fields: + +```cpp +struct UserProfile { + std::string username; + std::optional<std::string> bio; + std::optional<int> age; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(UserProfile, username, bio, age) +}; +``` + +`std::optional<T>` serializes as: +- The value `T` if it has a value +- `null` if it's `std::nullopt` + +With `_WITH_DEFAULT`, missing keys leave the optional as `std::nullopt`. + +## Collection Types + +Standard containers are automatically handled: + +```cpp +struct Team { + std::string name; + std::vector<Person> members; + std::map<std::string, int> scores; + std::set<std::string> tags; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(Team, name, members, scores, tags) +}; +``` + +Any type that satisfies the required type traits is automatically +serializable: +- Sequence containers (`std::vector`, `std::list`, `std::deque`, etc.) +- Associative containers (`std::map`, `std::set`, `std::unordered_map`, etc.) +- `std::pair`, `std::tuple` +- `std::array` + +## Smart Pointers + +`std::unique_ptr<T>` and `std::shared_ptr<T>` are supported if `T` is +serializable: + +```cpp +struct Node { + int value; + std::shared_ptr<Node> next; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Node, value, next) +}; +``` + +- Non-null pointer → serializes the pointed-to value +- Null pointer → serializes as JSON `null` +- JSON `null` → deserializes as null pointer + +## Type Traits + +The library uses SFINAE-based type traits to detect capabilities: + +| Trait | Purpose | +|---|---| +| `is_compatible_type` | Can be converted to/from JSON | +| `has_to_json` | Has a `to_json()` function | +| `has_from_json` | Has a `from_json()` function | +| `is_compatible_object_type` | Looks like a JSON object | +| `is_compatible_array_type` | Looks like a JSON array | +| `is_compatible_string_type` | Looks like a JSON string | +| `is_compatible_integer_type` | Looks like a JSON integer | + +These traits live in `include/nlohmann/detail/meta/type_traits.hpp`. diff --git a/docs/handbook/json4cpp/element-access.md b/docs/handbook/json4cpp/element-access.md new file mode 100644 index 0000000000..73d52126bb --- /dev/null +++ b/docs/handbook/json4cpp/element-access.md @@ -0,0 +1,581 @@ +# json4cpp — Element Access + +## Overview + +The `basic_json` class provides several ways to access elements: + +| Method | Applicable To | Behaviour on Missing | +|---|---|---| +| `operator[]` | array, object, null | Inserts default (creates if null) | +| `at()` | array, object | Throws `out_of_range` | +| `value()` | object | Returns caller-supplied default | +| `front()` | array, object, scalar | UB if empty | +| `back()` | array, object, scalar | UB if empty | +| `find()` | object | Returns `end()` | +| `contains()` | object | Returns `false` | +| `count()` | object | Returns `0` | + +## `operator[]` + +### Array Access + +```cpp +reference operator[](size_type idx); +const_reference operator[](size_type idx) const; +``` + +Accesses the element at index `idx`. If the JSON value is **null**, it is +automatically converted to an **array** before accessing: + +```cpp +json j; // null +j[0] = "first"; // j is now ["first"] +j[1] = "second"; // j is now ["first", "second"] +``` + +If `idx` is beyond the current array size, the array is extended with null +elements: + +```cpp +json j = {1, 2}; +j[5] = 99; +// j is now [1, 2, null, null, null, 99] +``` + +**Warning:** `const` array access does **not** extend the array and has +undefined behavior for out-of-bounds access. + +### Object Access + +```cpp +reference operator[](const typename object_t::key_type& key); +const_reference operator[](const typename object_t::key_type& key) const; + +// C++14 heterogeneous lookup (KeyType template) +template<typename KeyType> +reference operator[](KeyType&& key); +template<typename KeyType> +const_reference operator[](KeyType&& key) const; +``` + +Accesses the element with key `key`. If the key does not exist in a mutable +context, it is **inserted** with a null value: + +```cpp +json j = {{"name", "alice"}}; +j["age"] = 30; // inserts "age" +std::string name = j["name"]; + +// const access does not insert +const json& cj = j; +// cj["missing"]; // undefined behavior if key doesn't exist +``` + +If the JSON value is **null**, it is automatically converted to an **object**: + +```cpp +json j; // null +j["key"] = "value"; // j is now {"key": "value"} +``` + +### String Literal vs. Integer Ambiguity + +Be careful with `0`: + +```cpp +json j = {{"key", "value"}}; +// j[0] — array access (selects first element of object iteration) — NOT recommended +// j["key"] — object access +``` + +### Using `json::object_t::key_type` + +The non-const `operator[]` accepts a `key_type` (default: `std::string`). +The `KeyType` template overloads accept any type that satisfies these +constraints via `detail::is_usable_as_key_type`: + +- Must be comparable with `object_comparator_t` +- Not convertible to `basic_json` +- Not a `value_t` +- Not a `BasicJsonType` + +## `at()` + +### Array Access + +```cpp +reference at(size_type idx); +const_reference at(size_type idx) const; +``` + +Returns a reference to the element at index `idx`. Throws +`json::out_of_range` (id 401) if the index is out of bounds: + +```cpp +json j = {1, 2, 3}; +j.at(0); // 1 +j.at(3); // throws out_of_range::401: "array index 3 is out of range" +``` + +When `JSON_DIAGNOSTIC_POSITIONS` is enabled, the exception includes +byte-offset information. + +### Object Access + +```cpp +reference at(const typename object_t::key_type& key); +const_reference at(const typename object_t::key_type& key) const; + +template<typename KeyType> +reference at(KeyType&& key); +template<typename KeyType> +const_reference at(KeyType&& key) const; +``` + +Returns a reference to the element with key `key`. Throws +`json::out_of_range` (id 403) if the key is not found: + +```cpp +json j = {{"name", "alice"}, {"age", 30}}; +j.at("name"); // "alice" +j.at("missing"); // throws out_of_range::403: "key 'missing' not found" +``` + +### Type Mismatch + +Both `at()` overloads throw `json::type_error` (id 304) if the JSON value +is not of the expected type: + +```cpp +json j = 42; +j.at(0); // throws type_error::304: "cannot use at() with number" +j.at("key"); // throws type_error::304: "cannot use at() with number" +``` + +## `value()` + +```cpp +// With default value +ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const; + +// With JSON pointer +ValueType value(const json_pointer& ptr, const ValueType& default_value) const; + +// KeyType template overloads +template<typename KeyType> +ValueType value(KeyType&& key, const ValueType& default_value) const; +``` + +Returns the value for a given key or JSON pointer, or `default_value` if +the key/pointer does not resolve. Unlike `operator[]` and `at()`, this +method **never modifies** the JSON value. + +```cpp +json j = {{"name", "alice"}, {"age", 30}}; + +std::string name = j.value("name", "unknown"); // "alice" +std::string addr = j.value("address", "N/A"); // "N/A" +int height = j.value("height", 170); // 170 + +// With JSON pointer +int age = j.value("/age"_json_pointer, 0); // 30 +int foo = j.value("/foo"_json_pointer, -1); // -1 +``` + +Throws `json::type_error` (id 306) if the JSON value is not an object (for +the key overloads) or if the found value cannot be converted to `ValueType`. + +### `value()` vs `operator[]` + +| Feature | `operator[]` | `value()` | +|---|---|---| +| Modifies on miss | Yes (inserts null) | No | +| Returns | Reference | Value copy | +| Default on miss | null (always) | Caller-specified | +| Applicable to arrays | Yes | No (objects only) | + +## `front()` and `back()` + +```cpp +reference front(); +const_reference front() const; + +reference back(); +const_reference back() const; +``` + +Return references to the first/last element. For **arrays**, this is the +first/last element by index. For **objects**, this is the first/last element +by iteration order (which depends on the comparator — insertion order for +`ordered_json`). For **non-compound types**, the value itself is returned +(the JSON value is treated as a single-element container). + +```cpp +json j = {1, 2, 3}; +j.front(); // 1 +j.back(); // 3 + +json j2 = 42; +j2.front(); // 42 +j2.back(); // 42 +``` + +**Warning:** Calling `front()` or `back()` on an empty container is +**undefined behavior** (same as STL containers). + +## `find()` + +```cpp +iterator find(const typename object_t::key_type& key); +const_iterator find(const typename object_t::key_type& key) const; + +template<typename KeyType> +iterator find(KeyType&& key); +template<typename KeyType> +const_iterator find(KeyType&& key) const; +``` + +Returns an iterator to the element with the given key, or `end()` if not +found. Only works on objects: + +```cpp +json j = {{"name", "alice"}, {"age", 30}}; + +auto it = j.find("name"); +if (it != j.end()) { + std::cout << it.key() << " = " << it.value() << "\n"; +} + +auto it2 = j.find("missing"); +assert(it2 == j.end()); +``` + +For non-objects, `find()` always returns `end()`. + +## `contains()` + +```cpp +bool contains(const typename object_t::key_type& key) const; + +template<typename KeyType> +bool contains(KeyType&& key) const; + +// JSON pointer overload +bool contains(const json_pointer& ptr) const; +``` + +Returns `true` if the key or pointer exists: + +```cpp +json j = {{"name", "alice"}, {"address", {{"city", "wonderland"}}}}; + +j.contains("name"); // true +j.contains("phone"); // false + +// JSON pointer — checks nested paths +j.contains("/address/city"_json_pointer); // true +j.contains("/address/zip"_json_pointer); // false +``` + +## `count()` + +```cpp +size_type count(const typename object_t::key_type& key) const; + +template<typename KeyType> +size_type count(KeyType&& key) const; +``` + +Returns the number of elements with the given key. Since JSON objects have +unique keys, the result is always `0` or `1`: + +```cpp +json j = {{"name", "alice"}}; +j.count("name"); // 1 +j.count("missing"); // 0 +``` + +## `erase()` + +### Erase by Iterator + +```cpp +iterator erase(iterator pos); +iterator erase(const_iterator pos); +``` + +Removes the element at the given iterator position. Returns an iterator to +the element after the erased one: + +```cpp +json j = {1, 2, 3, 4, 5}; +auto it = j.erase(j.begin() + 2); // removes 3 +// j is now [1, 2, 4, 5], it points to 4 +``` + +### Erase by Iterator Range + +```cpp +iterator erase(iterator first, iterator last); +iterator erase(const_iterator first, const_iterator last); +``` + +Removes all elements in the range `[first, last)`: + +```cpp +json j = {1, 2, 3, 4, 5}; +j.erase(j.begin() + 1, j.begin() + 3); +// j is now [1, 4, 5] +``` + +### Erase by Key + +```cpp +size_type erase(const typename object_t::key_type& key); + +template<typename KeyType> +size_type erase(KeyType&& key); +``` + +Removes the element with the given key from an object. Returns the number +of elements removed (0 or 1): + +```cpp +json j = {{"name", "alice"}, {"age", 30}}; +j.erase("age"); +// j is now {"name": "alice"} +``` + +### Erase by Index + +```cpp +void erase(const size_type idx); +``` + +Removes the element at the given index from an array. Throws +`out_of_range::401` if the index is out of range: + +```cpp +json j = {"a", "b", "c"}; +j.erase(1); +// j is now ["a", "c"] +``` + +### Erase on Primitive Types + +Erasing by iterator on primitive types (number, string, boolean) is +supported only if the iterator points to the single element: + +```cpp +json j = 42; +j.erase(j.begin()); // j is now null +``` + +## `size()`, `empty()`, `max_size()` + +```cpp +size_type size() const noexcept; +bool empty() const noexcept; +size_type max_size() const noexcept; +``` + +| Type | `size()` | `empty()` | +|---|---|---| +| null | 0 | `true` | +| object | number of key-value pairs | `true` if no pairs | +| array | number of elements | `true` if no elements | +| scalar (string, number, boolean, binary) | 1 | `false` | + +```cpp +json j_null; +j_null.size(); // 0 +j_null.empty(); // true + +json j_arr = {1, 2, 3}; +j_arr.size(); // 3 +j_arr.empty(); // false + +json j_str = "hello"; +j_str.size(); // 1 +j_str.empty(); // false (primitive → always 1) +``` + +`max_size()` returns the maximum number of elements the container can hold +(delegates to the underlying container's `max_size()` for arrays and +objects; returns 1 for scalars). + +## `clear()` + +```cpp +void clear() noexcept; +``` + +Resets the value to a default-constructed value of the same type: + +| Type | Result after `clear()` | +|---|---| +| null | null | +| object | `{}` | +| array | `[]` | +| string | `""` | +| boolean | `false` | +| number_integer | `0` | +| number_unsigned | `0` | +| number_float | `0.0` | +| binary | `[]` (empty, no subtype) | + +## `push_back()` and `emplace_back()` + +### Array Operations + +```cpp +void push_back(basic_json&& val); +void push_back(const basic_json& val); + +template<typename... Args> +reference emplace_back(Args&&... args); +``` + +Appends an element at the end: + +```cpp +json j = {1, 2, 3}; +j.push_back(4); +j.emplace_back(5); +// j is now [1, 2, 3, 4, 5] +``` + +If the value is `null`, it's first converted to an empty array. + +### Object Operations + +```cpp +void push_back(const typename object_t::value_type& val); +void push_back(initializer_list_t init); +``` + +Inserts a key-value pair: + +```cpp +json j = {{"a", 1}}; +j.push_back({"b", 2}); // initializer_list pair +j.push_back(json::object_t::value_type("c", 3)); // explicit pair +// j is now {"a": 1, "b": 2, "c": 3} +``` + +### `operator+=` + +Alias for `push_back()`: + +```cpp +json j = {1, 2}; +j += 3; +j += {4, 5}; // pushes an array [4, 5] as a single element +``` + +## `emplace()` + +```cpp +template<typename... Args> +std::pair<iterator, bool> emplace(Args&&... args); +``` + +For objects, inserts a key-value pair if the key doesn't already exist. +Returns a pair of iterator and bool (whether insertion took place): + +```cpp +json j = {{"a", 1}}; +auto [it, inserted] = j.emplace("b", 2); +// inserted == true, it points to {"b": 2} +auto [it2, inserted2] = j.emplace("a", 99); +// inserted2 == false, existing value unchanged +``` + +## `insert()` + +### Array Insert + +```cpp +iterator insert(const_iterator pos, const basic_json& val); +iterator insert(const_iterator pos, basic_json&& val); +iterator insert(const_iterator pos, size_type cnt, const basic_json& val); +iterator insert(const_iterator pos, const_iterator first, const_iterator last); +iterator insert(const_iterator pos, initializer_list_t ilist); +``` + +Inserts elements at the given position: + +```cpp +json j = {1, 2, 5}; +j.insert(j.begin() + 2, 3); +j.insert(j.begin() + 3, 4); +// j is now [1, 2, 3, 4, 5] + +// Insert count copies +j.insert(j.end(), 2, 0); +// j is now [1, 2, 3, 4, 5, 0, 0] +``` + +### Object Insert + +```cpp +void insert(const_iterator first, const_iterator last); +``` + +Inserts elements from another object: + +```cpp +json j1 = {{"a", 1}}; +json j2 = {{"b", 2}, {"c", 3}}; +j1.insert(j2.begin(), j2.end()); +// j1 is now {"a": 1, "b": 2, "c": 3} +``` + +## `update()` + +```cpp +void update(const_reference j, bool merge_objects = false); +void update(const_iterator first, const_iterator last, bool merge_objects = false); +``` + +Updates an object with keys from another object. Existing keys are +**overwritten**: + +```cpp +json j1 = {{"a", 1}, {"b", 2}}; +json j2 = {{"b", 99}, {"c", 3}}; +j1.update(j2); +// j1 is now {"a": 1, "b": 99, "c": 3} +``` + +When `merge_objects` is `true`, nested objects are merged recursively +instead of being overwritten: + +```cpp +json j1 = {{"config", {{"debug", true}, {"port", 8080}}}}; +json j2 = {{"config", {{"port", 9090}, {"host", "localhost"}}}}; +j1.update(j2, true); +// j1["config"] is now {"debug": true, "port": 9090, "host": "localhost"} +``` + +## `swap()` + +```cpp +void swap(reference other) noexcept; + +void swap(array_t& other); +void swap(object_t& other); +void swap(string_t& other); +void swap(binary_t& other); +void swap(typename binary_t::container_type& other); +``` + +Swaps contents with another value or with a compatible container. +The typed overloads throw `type_error::310` if the types don't match: + +```cpp +json j = {1, 2, 3}; +std::vector<json> v = {4, 5, 6}; +j.swap(v); +// j is now [4, 5, 6], v contains the old j's elements +``` diff --git a/docs/handbook/json4cpp/exception-handling.md b/docs/handbook/json4cpp/exception-handling.md new file mode 100644 index 0000000000..58e52a4598 --- /dev/null +++ b/docs/handbook/json4cpp/exception-handling.md @@ -0,0 +1,368 @@ +# json4cpp — Exception Handling + +## Exception Hierarchy + +All exceptions derive from `json::exception`, which itself inherits from +`std::exception`. Defined in `include/nlohmann/detail/exceptions.hpp`: + +``` +std::exception + └── json::exception + ├── json::parse_error + ├── json::invalid_iterator + ├── json::type_error + ├── json::out_of_range + └── json::other_error +``` + +## Base Class: `json::exception` + +```cpp +class exception : public std::exception +{ +public: + const char* what() const noexcept override; + const int id; // numeric error identifier + +protected: + exception(int id_, const char* what_arg); + + static std::string name(const std::string& ename, int id_); + static std::string diagnostics(std::nullptr_t leaf_element); + static std::string diagnostics(const BasicJsonType* leaf_element); + +private: + std::runtime_error m; // stores the what() message +}; +``` + +### Error Message Format + +``` +[json.exception.<type>.<id>] <description> +``` + +Example: +``` +[json.exception.type_error.302] type must be string, but is number +``` + +### Diagnostics Mode + +When `JSON_DIAGNOSTICS` is enabled, error messages include the **path** +from the root to the problematic value: + +```cpp +#define JSON_DIAGNOSTICS 1 +#include <nlohmann/json.hpp> +``` + +Error message with diagnostics: +``` +[json.exception.type_error.302] (/config/server/port) type must be string, but is number +``` + +The path is computed by walking up the parent chain (stored in each +`basic_json` node when diagnostics are enabled). + +## `parse_error` + +Thrown when parsing fails. + +```cpp +class parse_error : public exception +{ +public: + const std::size_t byte; // byte position of the error + + static parse_error create(int id_, const position_t& pos, + const std::string& what_arg, + BasicJsonType* context); + static parse_error create(int id_, std::size_t byte_, + const std::string& what_arg, + BasicJsonType* context); +}; +``` + +### Error IDs + +| ID | Condition | Example | +|---|---|---| +| 101 | Unexpected token | `parse("}")` | +| 102 | Invalid `\u` escape | `parse("\"\\u000g\"")` | +| 103 | Invalid surrogate pair | `parse("\"\\uDC00\"")` | +| 104 | Invalid JSON Patch | `patch(json::array({42}))` | +| 105 | JSON Patch missing field | `patch(json::array({{{"op","add"}}}))` | +| 106 | Number overflow | Very large number literal | +| 107 | Invalid JSON Pointer | `json_pointer("no-slash")` | +| 108 | Invalid Unicode code point | Code point > U+10FFFF | +| 109 | Invalid UTF-8 in input | Binary data as string | +| 110 | Binary format marker error | Invalid CBOR/MsgPack byte | +| 112 | BSON parse error | Malformed BSON input | +| 113 | UBJSON parse error | Invalid UBJSON type marker | +| 114 | BJData parse error | Invalid BJData structure | +| 115 | Incomplete binary input | Truncated binary data | + +### Catching Parse Errors + +```cpp +try { + json j = json::parse("{invalid}"); +} catch (json::parse_error& e) { + std::cerr << "Parse error: " << e.what() << "\n"; + std::cerr << "Error ID: " << e.id << "\n"; + std::cerr << "Byte position: " << e.byte << "\n"; +} +``` + +### Avoiding Exceptions + +```cpp +json j = json::parse("invalid", nullptr, false); +if (j.is_discarded()) { + // Handle parse failure without exception +} +``` + +## `type_error` + +Thrown when a method is called on a JSON value of the wrong type. + +```cpp +class type_error : public exception +{ +public: + static type_error create(int id_, const std::string& what_arg, + BasicJsonType* context); +}; +``` + +### Error IDs + +| ID | Condition | Example | +|---|---|---| +| 301 | Cannot create from type | `json j = std::complex<double>()` | +| 302 | Type mismatch in get | `json("str").get<int>()` | +| 303 | Type mismatch in get_ref | `json(42).get_ref<std::string&>()` | +| 304 | Wrong type for at() | `json(42).at("key")` | +| 305 | Wrong type for operator[] | `json("str")[0]` | +| 306 | Wrong type for value() | `json(42).value("k", 0)` | +| 307 | Cannot erase from type | `json(42).erase(0)` | +| 308 | Wrong type for push_back | `json("str").push_back(1)` | +| 309 | Wrong type for insert | `json(42).insert(...)` | +| 310 | Wrong type for swap | `json(42).swap(vec)` | +| 311 | Wrong type for iterator | `json(42).begin().key()` | +| 312 | Cannot serialize binary to text | `json::binary(...).dump()` | +| 313 | Wrong type for push_back pair | `json(42).push_back({"k",1})` | +| 314 | unflatten: value not primitive | `json({"/a": [1]}).unflatten()` | +| 315 | unflatten: conflicting paths | `json({"/a": 1, "/a/b": 2}).unflatten()` | +| 316 | Invalid UTF-8 in dump (strict) | Invalid byte in string | +| 317 | to_bson: top level not object | `json::to_bson(json::array())` | +| 318 | to_bson: key too long | Key > max int32 length | + +### Common Type Errors + +```cpp +json j = 42; + +// 302: type mismatch +try { + std::string s = j.get<std::string>(); +} catch (json::type_error& e) { + // type must be string, but is number +} + +// 304: wrong type for at() +try { + j.at("key"); +} catch (json::type_error& e) { + // cannot use at() with number +} + +// 316: invalid UTF-8 +try { + json j = std::string("\xC0\xAF"); // overlong encoding + j.dump(); +} catch (json::type_error& e) { + // invalid utf-8 byte +} +``` + +## `out_of_range` + +Thrown when accessing elements outside valid bounds. + +```cpp +class out_of_range : public exception +{ +public: + static out_of_range create(int id_, const std::string& what_arg, + BasicJsonType* context); +}; +``` + +### Error IDs + +| ID | Condition | Example | +|---|---|---| +| 401 | Array index out of range | `json({1,2}).at(5)` | +| 402 | Array index `-` in at() | `j.at("/-"_json_pointer)` | +| 403 | Key not found | `j.at("missing")` | +| 404 | JSON Pointer reference error | `j["/bad/path"_json_pointer]` | +| 405 | back()/pop_back() on empty ptr | `json_pointer("").back()` | +| 406 | Numeric overflow in get | Large float → int | +| 407 | Number not representable | `json(1e500).get<int>()` | +| 408 | BSON key conflict | Key "0" in BSON array | + +```cpp +json j = {1, 2, 3}; + +try { + j.at(10); +} catch (json::out_of_range& e) { + // [json.exception.out_of_range.401] array index 10 is out of range +} +``` + +## `invalid_iterator` + +Thrown when iterators are used incorrectly. + +```cpp +class invalid_iterator : public exception +{ +public: + static invalid_iterator create(int id_, const std::string& what_arg, + BasicJsonType* context); +}; +``` + +### Error IDs + +| ID | Condition | +|---|---| +| 201 | Iterator not dereferenceable | +| 202 | Iterator += on non-array | +| 203 | Iterator compare across values | +| 204 | Iterator - on non-array | +| 205 | Iterator > on non-array | +| 206 | Iterator + on non-array | +| 207 | Cannot use key() on array iterator | +| 209 | Range [first, last) not from same container | +| 210 | Range not valid for erase | +| 211 | Range not valid for insert | +| 212 | Range from different container in insert | +| 213 | Insert iterator for non-array | +| 214 | Insert range for non-object | + +```cpp +json j1 = {1, 2, 3}; +json j2 = {4, 5, 6}; + +try { + j1.erase(j2.begin()); // wrong container +} catch (json::invalid_iterator& e) { + // iterator does not fit current value +} +``` + +## `other_error` + +Thrown for miscellaneous errors that don't fit the other categories. + +```cpp +class other_error : public exception +{ +public: + static other_error create(int id_, const std::string& what_arg, + BasicJsonType* context); +}; +``` + +### Error IDs + +| ID | Condition | +|---|---| +| 501 | JSON Patch test operation failed | + +```cpp +json doc = {{"name", "alice"}}; +json patch = json::array({ + {{"op", "test"}, {"path", "/name"}, {"value", "bob"}} +}); + +try { + doc.patch(patch); +} catch (json::other_error& e) { + // [json.exception.other_error.501] unsuccessful: /name +} +``` + +## Exception-Free API + +### `parse()` with `allow_exceptions = false` + +```cpp +json j = json::parse("invalid", nullptr, false); +if (j.is_discarded()) { + // Handle gracefully +} +``` + +### `get()` Alternatives + +Use `value()` for safe object access with defaults: + +```cpp +json j = {{"timeout", 30}}; +int t = j.value("timeout", 60); // 30 +int r = j.value("retries", 3); // 3 (missing key) +``` + +Use `contains()` before access: + +```cpp +if (j.contains("key")) { + auto val = j["key"]; +} +``` + +Use `find()` for iterator-based access: + +```cpp +auto it = j.find("key"); +if (it != j.end()) { + // use *it +} +``` + +### Type Checking Before Access + +```cpp +json j = /* unknown content */; + +if (j.is_string()) { + auto s = j.get<std::string>(); +} +``` + +## Catching All JSON Exceptions + +```cpp +try { + // JSON operations +} catch (json::exception& e) { + std::cerr << "JSON error [" << e.id << "]: " << e.what() << "\n"; +} +``` + +Since `json::exception` derives from `std::exception`, it can also be +caught generically: + +```cpp +try { + // ... +} catch (const std::exception& e) { + std::cerr << e.what() << "\n"; +} +``` diff --git a/docs/handbook/json4cpp/iteration.md b/docs/handbook/json4cpp/iteration.md new file mode 100644 index 0000000000..be32a21ea8 --- /dev/null +++ b/docs/handbook/json4cpp/iteration.md @@ -0,0 +1,339 @@ +# json4cpp — Iteration + +## Iterator Types + +The `basic_json` class provides a full set of iterators modeled after STL +container iterators. All are defined in +`include/nlohmann/detail/iterators/`: + +| Type | Class | Header | +|---|---|---| +| `iterator` | `iter_impl<basic_json>` | `iter_impl.hpp` | +| `const_iterator` | `iter_impl<const basic_json>` | `iter_impl.hpp` | +| `reverse_iterator` | `json_reverse_iterator<iterator>` | `json_reverse_iterator.hpp` | +| `const_reverse_iterator` | `json_reverse_iterator<const_iterator>` | `json_reverse_iterator.hpp` | + +## `iter_impl` Internals + +The `iter_impl<BasicJsonType>` template is the core iterator +implementation. It wraps an `internal_iterator` struct: + +```cpp +struct internal_iterator +{ + typename BasicJsonType::object_t::iterator object_iterator; + typename BasicJsonType::array_t::iterator array_iterator; + primitive_iterator_t primitive_iterator; +}; +``` + +Only one of these three fields is active at a time, determined by the +`m_object` pointer's `type()`: + +- **Object**: uses `object_iterator` (delegates to the underlying map/ordered_map iterator) +- **Array**: uses `array_iterator` (delegates to `std::vector::iterator`) +- **Primitive** (null, boolean, number, string, binary): uses `primitive_iterator_t` + +### `primitive_iterator_t` + +Primitive types are treated as single-element containers. The +`primitive_iterator_t` is a wrapper around `std::ptrdiff_t`: + +- Value `0` → points to the element (equivalent to `begin()`) +- Value `1` → past-the-end (equivalent to `end()`) +- Value `-1` → before-the-begin (sentinel, `end_value`) + +```cpp +json j = 42; +for (auto it = j.begin(); it != j.end(); ++it) { + // executes exactly once, *it == 42 +} +``` + +## Range Functions + +### Forward Iteration + +```cpp +iterator begin() noexcept; +const_iterator begin() const noexcept; +const_iterator cbegin() const noexcept; + +iterator end() noexcept; +const_iterator end() const noexcept; +const_iterator cend() const noexcept; +``` + +```cpp +json j = {1, 2, 3}; +for (auto it = j.begin(); it != j.end(); ++it) { + std::cout << *it << " "; +} +// Output: 1 2 3 +``` + +### Reverse Iteration + +```cpp +reverse_iterator rbegin() noexcept; +const_reverse_iterator rbegin() const noexcept; +const_reverse_iterator crbegin() const noexcept; + +reverse_iterator rend() noexcept; +const_reverse_iterator rend() const noexcept; +const_reverse_iterator crend() const noexcept; +``` + +```cpp +json j = {1, 2, 3}; +for (auto it = j.rbegin(); it != j.rend(); ++it) { + std::cout << *it << " "; +} +// Output: 3 2 1 +``` + +## Range-Based For Loops + +### Simple Iteration + +```cpp +json j = {"alpha", "beta", "gamma"}; +for (const auto& element : j) { + std::cout << element << "\n"; +} +``` + +### Mutable Iteration + +```cpp +json j = {1, 2, 3}; +for (auto& element : j) { + element = element.get<int>() * 2; +} +// j is now [2, 4, 6] +``` + +### Object Iteration + +When iterating over objects, each element is a **value** (not a key-value +pair). Use `it.key()` and `it.value()` on an explicit iterator, or use +`items()`: + +```cpp +json j = {{"name", "alice"}, {"age", 30}}; + +// Method 1: explicit iterator +for (auto it = j.begin(); it != j.end(); ++it) { + std::cout << it.key() << ": " << it.value() << "\n"; +} + +// Method 2: range-for gives values only +for (const auto& val : j) { + // val is the value, key is not accessible +} +``` + +## `items()` — Key-Value Iteration + +```cpp +iteration_proxy<iterator> items() noexcept; +iteration_proxy<const_iterator> items() const noexcept; +``` + +Returns an `iteration_proxy` that wraps the iterator to provide `key()` +and `value()` accessors in range-based for loops: + +```cpp +json j = {{"name", "alice"}, {"age", 30}, {"active", true}}; + +for (auto& [key, value] : j.items()) { + std::cout << key << " = " << value << "\n"; +} +``` + +### Array Keys + +For arrays, `items()` synthesizes string keys from the index: + +```cpp +json j = {"a", "b", "c"}; + +for (auto& [key, value] : j.items()) { + std::cout << key << ": " << value << "\n"; +} +// Output: +// 0: "a" +// 1: "b" +// 2: "c" +``` + +### Primitive Keys + +For primitives, the key is always `""` (empty string): + +```cpp +json j = 42; +for (auto& [key, value] : j.items()) { + // key == "", value == 42 +} +``` + +## `iteration_proxy` Implementation + +Defined in `include/nlohmann/detail/iterators/iteration_proxy.hpp`: + +```cpp +template<typename IteratorType> +class iteration_proxy +{ + class iteration_proxy_value + { + IteratorType anchor; // underlying iterator + std::size_t array_index = 0; // cached index for arrays + mutable std::size_t array_index_last = 0; + mutable string_type array_index_str = "0"; + + const string_type& key() const; + typename IteratorType::reference value() const; + }; + +public: + iteration_proxy_value begin() const noexcept; + iteration_proxy_value end() const noexcept; +}; +``` + +The `key()` method dispatches based on the value type: +- **Array**: converts `array_index` to string (`std::to_string`) +- **Object**: returns `anchor.key()` +- **Other**: returns empty string + +## Structured Bindings + +C++17 structured bindings work with `items()`: + +```cpp +json j = {{"x", 1}, {"y", 2}}; +for (const auto& [key, value] : j.items()) { + // key is const std::string&, value is const json& +} +``` + +This is enabled by specializations in `<nlohmann/detail/iterators/iteration_proxy.hpp>`: + +```cpp +namespace std { + template<std::size_t N, typename IteratorType> + struct tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType>>; + + template<typename IteratorType> + struct tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>>; +} +``` + +## Iterator Arithmetic (Arrays Only) + +Array iterators support random access: + +```cpp +json j = {10, 20, 30, 40, 50}; + +auto it = j.begin(); +it += 2; // points to 30 +auto it2 = it + 1; // points to 40 +auto diff = it2 - it; // 1 + +j[it - j.begin()]; // equivalent of *it +``` + +Object iterators support only increment and decrement (bidirectional). + +## `json_reverse_iterator` + +Extends `std::reverse_iterator` with `key()` and `value()` methods: + +```cpp +template<typename Base> +class json_reverse_iterator : public std::reverse_iterator<Base> +{ +public: + // Inherited from std::reverse_iterator: + // operator*, operator->, operator++, operator--, operator+, operator- ... + + // Added: + const typename Base::key_type& key() const; + typename Base::reference value() const; +}; +``` + +```cpp +json j = {{"a", 1}, {"b", 2}, {"c", 3}}; +for (auto it = j.rbegin(); it != j.rend(); ++it) { + std::cout << it.key() << ": " << it.value() << "\n"; +} +// Output (reversed iteration order) +``` + +## Iterator Invalidation + +Iterator invalidation follows the rules of the underlying containers: + +| Operation | Object (`std::map`) | Array (`std::vector`) | +|---|---|---| +| `push_back()` | Not invalidated | May invalidate all | +| `insert()` | Not invalidated | Invalidates at/after pos | +| `erase()` | Only erased | At/after erased pos | +| `clear()` | All invalidated | All invalidated | +| `operator[]` (new key) | Not invalidated | May invalidate all | + +For `ordered_json` (backed by `std::vector`), all iterators may be +invalidated on any insertion/erasure since the ordered_map inherits from +`std::vector`. + +## Iterating Null Values + +Null values behave as empty containers: + +```cpp +json j; // null +for (const auto& el : j) { + // never executes +} +assert(j.begin() == j.end()); +``` + +## Complete Example + +```cpp +#include <nlohmann/json.hpp> +#include <iostream> + +using json = nlohmann::json; + +int main() { + json config = { + {"server", { + {"host", "localhost"}, + {"port", 8080}, + {"features", {"auth", "logging", "metrics"}} + }}, + {"debug", false} + }; + + // Iterate top-level keys + for (auto& [key, value] : config.items()) { + std::cout << key << " [" << value.type_name() << "]\n"; + } + + // Iterate nested array + for (const auto& feature : config["server"]["features"]) { + std::cout << " feature: " << feature << "\n"; + } + + // Reverse iterate + auto& features = config["server"]["features"]; + for (auto it = features.rbegin(); it != features.rend(); ++it) { + std::cout << " reverse: " << *it << "\n"; + } +} +``` diff --git a/docs/handbook/json4cpp/json-patch.md b/docs/handbook/json4cpp/json-patch.md new file mode 100644 index 0000000000..4c9de8fad5 --- /dev/null +++ b/docs/handbook/json4cpp/json-patch.md @@ -0,0 +1,341 @@ +# json4cpp — JSON Patch & Merge Patch + +## JSON Patch (RFC 6902) + +JSON Patch defines a JSON document structure for expressing a sequence of +operations to apply to a JSON document. + +### `patch()` + +```cpp +basic_json patch(const basic_json& json_patch) const; +``` + +Returns a new JSON value with the patch applied. Does not modify the +original. Throws `parse_error::104` if the patch document is malformed. + +```cpp +json doc = { + {"name", "alice"}, + {"age", 30}, + {"scores", {90, 85}} +}; + +json patch = json::array({ + {{"op", "replace"}, {"path", "/name"}, {"value", "bob"}}, + {{"op", "add"}, {"path", "/scores/-"}, {"value", 95}}, + {{"op", "remove"}, {"path", "/age"}} +}); + +json result = doc.patch(patch); +// {"name": "bob", "scores": [90, 85, 95]} +``` + +### `patch_inplace()` + +```cpp +void patch_inplace(const basic_json& json_patch); +``` + +Applies the patch directly to the JSON value (modifying in place): + +```cpp +json doc = {{"key", "old"}}; +doc.patch_inplace(json::array({ + {{"op", "replace"}, {"path", "/key"}, {"value", "new"}} +})); +// doc is now {"key": "new"} +``` + +### Patch Operations + +Each operation is a JSON object with an `"op"` field and operation-specific +fields: + +#### `add` + +Adds a value at the target location. If the target exists and is in an +object, it is replaced. If the target is in an array, the value is inserted +before the specified index. + +```json +{"op": "add", "path": "/a/b", "value": 42} +``` + +The path's parent must exist. The `-` token appends to arrays: + +```cpp +json doc = {{"arr", {1, 2}}}; +json p = json::array({{{"op", "add"}, {"path", "/arr/-"}, {"value", 3}}}); +doc.patch(p); // {"arr": [1, 2, 3]} +``` + +#### `remove` + +Removes the value at the target location: + +```json +{"op": "remove", "path": "/a/b"} +``` + +Throws `out_of_range` if the path does not exist. + +#### `replace` + +Replaces the value at the target location (equivalent to `remove` + `add`): + +```json +{"op": "replace", "path": "/name", "value": "bob"} +``` + +Throws `out_of_range` if the path does not exist. + +#### `move` + +Moves a value from one location to another: + +```json +{"op": "move", "from": "/a/b", "path": "/c/d"} +``` + +Equivalent to `remove` from source + `add` to target. The `from` path +must not be a prefix of the `path`. + +#### `copy` + +Copies a value from one location to another: + +```json +{"op": "copy", "from": "/a/b", "path": "/c/d"} +``` + +#### `test` + +Tests that the value at the target location equals the specified value: + +```json +{"op": "test", "path": "/name", "value": "alice"} +``` + +If the test fails, `patch()` throws `other_error::501`: + +```cpp +json doc = {{"name", "alice"}}; +json p = json::array({ + {{"op", "test"}, {"path", "/name"}, {"value", "bob"}} +}); + +try { + doc.patch(p); +} catch (json::other_error& e) { + // [json.exception.other_error.501] unsuccessful: ... +} +``` + +### Patch Validation + +The `patch()` method validates each operation: +- `op` must be one of: `add`, `remove`, `replace`, `move`, `copy`, `test` +- `path` is required for all operations +- `value` is required for `add`, `replace`, `test` +- `from` is required for `move`, `copy` + +Missing or invalid fields throw `parse_error::105`. + +### Operation Order + +Operations are applied sequentially. Each operation acts on the result of +the previous one: + +```cpp +json doc = {}; +json ops = json::array({ + {{"op", "add"}, {"path", "/a"}, {"value", 1}}, + {{"op", "add"}, {"path", "/b"}, {"value", 2}}, + {{"op", "replace"}, {"path", "/a"}, {"value", 10}}, + {{"op", "remove"}, {"path", "/b"}} +}); + +json result = doc.patch(ops); +// {"a": 10} +``` + +## `diff()` — Computing Patches + +```cpp +static basic_json diff(const basic_json& source, + const basic_json& target, + const string_t& path = ""); +``` + +Generates a JSON Patch that transforms `source` into `target`: + +```cpp +json source = {{"name", "alice"}, {"age", 30}}; +json target = {{"name", "alice"}, {"age", 31}, {"city", "wonderland"}}; + +json patch = json::diff(source, target); +// [ +// {"op": "replace", "path": "/age", "value": 31}, +// {"op": "add", "path": "/city", "value": "wonderland"} +// ] + +// Verify roundtrip +assert(source.patch(patch) == target); +``` + +### Diff Algorithm + +The algorithm works recursively: +1. If `source == target`, produce no operations +2. If types differ, produce a `replace` operation +3. If both are objects: + - Keys in `source` but not `target` → `remove` + - Keys in `target` but not `source` → `add` + - Keys in both with different values → recurse +4. If both are arrays: + - Compare element-by-element + - Produce `replace` for changed elements + - Produce `add` for extra elements in target + - Produce `remove` for extra elements in source +5. For primitives with different values → `replace` + +Note: The generated patch uses only `add`, `remove`, and `replace` +operations (not `move` or `copy`). + +### Custom Base Path + +The `path` parameter sets a prefix for all generated paths: + +```cpp +json patch = json::diff(a, b, "/config"); +// All paths will start with "/config/..." +``` + +## Merge Patch (RFC 7396) + +Merge Patch is a simpler alternative to JSON Patch. Instead of an array of +operations, a merge patch is a JSON object that describes the desired +changes directly. + +### `merge_patch()` + +```cpp +void merge_patch(const basic_json& apply_patch); +``` + +Applies a merge patch to the JSON value in place: + +```cpp +json doc = { + {"title", "Hello"}, + {"author", {{"name", "alice"}}}, + {"tags", {"example"}} +}; + +json patch = { + {"title", "Goodbye"}, + {"author", {{"name", "bob"}}}, + {"tags", nullptr} // null means "remove" +}; + +doc.merge_patch(patch); +// { +// "title": "Goodbye", +// "author": {"name": "bob"}, +// } +// "tags" was removed because the patch value was null +``` + +### Merge Patch Rules + +The merge patch algorithm (per RFC 7396): + +1. If the patch is not an object, replace the target entirely +2. If the patch is an object: + - For each key in the patch: + - If the value is `null`, remove the key from the target + - Otherwise, recursively merge_patch the target's key with the value + +```cpp +// Partial update — only specified fields change +json config = {{"debug", false}, {"port", 8080}, {"host", "0.0.0.0"}}; + +config.merge_patch({{"port", 9090}}); +// {"debug": false, "port": 9090, "host": "0.0.0.0"} + +config.merge_patch({{"debug", nullptr}}); +// {"port": 9090, "host": "0.0.0.0"} +``` + +### Limitations of Merge Patch + +- Cannot set a value to `null` (null means "delete") +- Cannot manipulate arrays — arrays are replaced entirely +- Cannot express "move" or "copy" semantics + +```cpp +json doc = {{"items", {1, 2, 3}}}; +doc.merge_patch({{"items", {4, 5}}}); +// {"items": [4, 5]} — array replaced, not merged +``` + +## JSON Patch vs. Merge Patch + +| Feature | JSON Patch (RFC 6902) | Merge Patch (RFC 7396) | +|---|---|---| +| Format | Array of operations | JSON object | +| Operations | add, remove, replace, move, copy, test | Implicit merge | +| Array handling | Per-element operations | Replace entire array | +| Set value to null | Yes (explicit `add`/`replace`) | No (null = delete) | +| Test assertions | Yes (`test` op) | No | +| Reversibility | Can `diff()` to reverse | No | +| Complexity | More verbose | Simpler | + +## Complete Example + +```cpp +#include <nlohmann/json.hpp> +#include <iostream> + +using json = nlohmann::json; + +int main() { + // Original document + json doc = { + {"name", "Widget"}, + {"version", "1.0"}, + {"settings", { + {"color", "blue"}, + {"size", 10}, + {"enabled", true} + }}, + {"tags", {"production", "stable"}} + }; + + // JSON Patch: precise operations + json patch = json::array({ + {{"op", "replace"}, {"path", "/version"}, {"value", "2.0"}}, + {{"op", "add"}, {"path", "/settings/theme"}, {"value", "dark"}}, + {{"op", "remove"}, {"path", "/settings/size"}}, + {{"op", "add"}, {"path", "/tags/-"}, {"value", "updated"}}, + {{"op", "test"}, {"path", "/name"}, {"value", "Widget"}} + }); + + json patched = doc.patch(patch); + + // Compute diff to verify + json computed_patch = json::diff(doc, patched); + assert(doc.patch(computed_patch) == patched); + + // Merge Patch: simple update + json merge = { + {"version", "2.1"}, + {"settings", {{"color", "red"}}}, + {"tags", nullptr} // remove tags + }; + + patched.merge_patch(merge); + std::cout << patched.dump(2) << "\n"; +} +``` diff --git a/docs/handbook/json4cpp/json-pointer.md b/docs/handbook/json4cpp/json-pointer.md new file mode 100644 index 0000000000..0fb0283fe9 --- /dev/null +++ b/docs/handbook/json4cpp/json-pointer.md @@ -0,0 +1,361 @@ +# json4cpp — JSON Pointer (RFC 6901) + +## Overview + +JSON Pointer (RFC 6901) provides a string syntax for identifying a specific +value within a JSON document. The library implements this as the +`json_pointer` class template, defined in +`include/nlohmann/detail/json_pointer.hpp`. + +```cpp +template<typename RefStringType> +class json_pointer +{ + friend class basic_json; + + std::vector<string_t> reference_tokens; // parsed path segments +}; +``` + +The default alias is: + +```cpp +using json_pointer = json_pointer<std::string>; +``` + +## Syntax + +A JSON Pointer is a string of zero or more tokens separated by `/`: + +``` +"" → whole document +"/foo" → key "foo" in root object +"/foo/0" → first element of array at key "foo" +"/a~1b" → key "a/b" (escaped /) +"/m~0n" → key "m~n" (escaped ~) +``` + +### Escape Sequences + +| Sequence | Represents | +|---|---| +| `~0` | `~` | +| `~1` | `/` | + +Escaping is applied **before** splitting (per RFC 6901 §3). + +## Construction + +### From String + +```cpp +json_pointer(const string_t& s = ""); +``` + +Parses the pointer string and populates `reference_tokens`. Throws +`parse_error::107` if the string is not a valid JSON Pointer (e.g., +a non-empty string that doesn't start with `/`): + +```cpp +json_pointer ptr("/foo/bar/0"); + +// Invalid: +// json_pointer ptr("foo"); // parse_error::107 — must start with / +``` + +### User-Defined Literal + +```cpp +using namespace nlohmann::literals; + +auto ptr = "/server/host"_json_pointer; +``` + +## Accessing Values + +### `operator[]` with Pointer + +```cpp +json j = {{"server", {{"host", "localhost"}, {"port", 8080}}}}; + +j["/server/host"_json_pointer]; // "localhost" +j["/server/port"_json_pointer]; // 8080 +j["/server"_json_pointer]; // {"host":"localhost","port":8080} +``` + +### `at()` with Pointer + +```cpp +json j = {{"a", {{"b", 42}}}}; + +j.at("/a/b"_json_pointer); // 42 +j.at("/a/missing"_json_pointer); // throws out_of_range::403 +``` + +### `value()` with Pointer + +```cpp +json j = {{"timeout", 30}}; + +j.value("/timeout"_json_pointer, 60); // 30 +j.value("/retries"_json_pointer, 3); // 3 (key not found, returns default) +``` + +### `contains()` with Pointer + +```cpp +json j = {{"a", {{"b", 42}}}}; + +j.contains("/a/b"_json_pointer); // true +j.contains("/a/c"_json_pointer); // false +j.contains("/x"_json_pointer); // false +``` + +## Pointer Manipulation + +### `to_string()` + +```cpp +string_t to_string() const; +``` + +Reconstructs the pointer string with proper escaping: + +```cpp +json_pointer ptr("/a~1b/0"); +ptr.to_string(); // "/a~1b/0" +``` + +### `operator string_t()` + +Implicit conversion to string (same as `to_string()`). + +### `operator/=` — Append Token + +```cpp +json_pointer& operator/=(const string_t& token); +json_pointer& operator/=(std::size_t array_index); +``` + +Appends a reference token: + +```cpp +json_pointer ptr("/a"); +ptr /= "b"; // "/a/b" +ptr /= 0; // "/a/b/0" +``` + +### `operator/` — Concatenate + +```cpp +friend json_pointer operator/(const json_pointer& lhs, const string_t& token); +friend json_pointer operator/(const json_pointer& lhs, std::size_t array_index); +friend json_pointer operator/(const json_pointer& lhs, const json_pointer& rhs); +``` + +```cpp +auto ptr = "/a"_json_pointer / "b" / 0; // "/a/b/0" +auto combined = "/a"_json_pointer / "/b/c"_json_pointer; // "/a/b/c" +``` + +### `parent_pointer()` + +```cpp +json_pointer parent_pointer() const; +``` + +Returns the parent pointer (all tokens except the last): + +```cpp +auto ptr = "/a/b/c"_json_pointer; +ptr.parent_pointer().to_string(); // "/a/b" + +auto root = ""_json_pointer; +root.parent_pointer().to_string(); // "" (root's parent is root) +``` + +### `back()` + +```cpp +const string_t& back() const; +``` + +Returns the last reference token: + +```cpp +auto ptr = "/a/b/c"_json_pointer; +ptr.back(); // "c" +``` + +Throws `out_of_range::405` if the pointer is empty (root). + +### `push_back()` + +```cpp +void push_back(const string_t& token); +void push_back(string_t&& token); +``` + +Appends a token: + +```cpp +json_pointer ptr; +ptr.push_back("a"); +ptr.push_back("b"); +ptr.to_string(); // "/a/b" +``` + +### `pop_back()` + +```cpp +void pop_back(); +``` + +Removes the last token: + +```cpp +auto ptr = "/a/b/c"_json_pointer; +ptr.pop_back(); +ptr.to_string(); // "/a/b" +``` + +Throws `out_of_range::405` if the pointer is empty. + +### `empty()` + +```cpp +bool empty() const noexcept; +``` + +Returns `true` if the pointer has no reference tokens (i.e., it refers to +the whole document): + +```cpp +json_pointer("").empty(); // true (root pointer) +json_pointer("/a").empty(); // false +``` + +## Array Indexing + +JSON Pointer uses string tokens for array indices. The token `"0"` refers +to the first element, `"1"` to the second, etc.: + +```cpp +json j = {"a", "b", "c"}; + +j["/0"_json_pointer]; // "a" +j["/1"_json_pointer]; // "b" +j["/2"_json_pointer]; // "c" +``` + +### The `-` Token + +The special token `-` refers to the "past-the-end" position in an array. +It can be used with `operator[]` to **append** to an array: + +```cpp +json j = {1, 2, 3}; +j["/-"_json_pointer] = 4; +// j is now [1, 2, 3, 4] +``` + +Using `-` with `at()` throws `out_of_range::402` since there's no element +at that position. + +## `flatten()` and `unflatten()` + +### `flatten()` + +```cpp +basic_json flatten() const; +``` + +Converts a nested JSON value into a flat object where each key is a JSON +Pointer and each value is a primitive: + +```cpp +json j = { + {"name", "alice"}, + {"address", { + {"city", "wonderland"}, + {"zip", "12345"} + }}, + {"scores", {90, 85, 92}} +}; + +json flat = j.flatten(); +// { +// "/name": "alice", +// "/address/city": "wonderland", +// "/address/zip": "12345", +// "/scores/0": 90, +// "/scores/1": 85, +// "/scores/2": 92 +// } +``` + +### `unflatten()` + +```cpp +basic_json unflatten() const; +``` + +The inverse of `flatten()`. Reconstructs a nested structure from a flat +pointer-keyed object: + +```cpp +json flat = { + {"/a/b", 1}, + {"/a/c", 2}, + {"/d", 3} +}; + +json nested = flat.unflatten(); +// {"a": {"b": 1, "c": 2}, "d": 3} +``` + +Throws `type_error::314` if a value is not primitive, or +`type_error::315` if values at a path conflict (e.g., both +`/a` and `/a/b` have values). + +### Roundtrip + +```cpp +json j = /* any JSON value */; +assert(j == j.flatten().unflatten()); +``` + +Note: `unflatten()` cannot reconstruct arrays from flattened form since +numeric keys (`/0`, `/1`) become object keys. The result will have +object-typed containers where the original had arrays. + +## Internal Implementation + +### Token Resolution + +The `get_checked()` and `get_unchecked()` methods resolve a pointer +against a JSON value by walking through the reference tokens: + +```cpp +// Simplified logic +BasicJsonType* ptr = &value; +for (const auto& token : reference_tokens) { + if (ptr->is_object()) { + ptr = &ptr->at(token); + } else if (ptr->is_array()) { + ptr = &ptr->at(std::stoi(token)); + } +} +return *ptr; +``` + +### Error IDs + +| ID | Condition | +|---|---| +| `parse_error::107` | Invalid pointer syntax | +| `out_of_range::401` | Array index out of range | +| `out_of_range::402` | Array index `-` used with `at()` | +| `out_of_range::403` | Key not found in object | +| `out_of_range::404` | Unresolved reference token | +| `out_of_range::405` | `back()` / `pop_back()` on empty pointer | diff --git a/docs/handbook/json4cpp/overview.md b/docs/handbook/json4cpp/overview.md new file mode 100644 index 0000000000..6737ebcc6a --- /dev/null +++ b/docs/handbook/json4cpp/overview.md @@ -0,0 +1,330 @@ +# json4cpp — Overview + +## What is json4cpp? + +json4cpp is the Project-Tick vendored copy of **nlohmann/json** (version 3.12.0), +a header-only C++ library for working with JSON data. Created by Niels Lohmann, +it provides a first-class JSON type (`nlohmann::json`) that behaves like an STL +container and integrates seamlessly with modern C++ idioms. + +The library is designed around one central class template: + +```cpp +template< + template<typename U, typename V, typename... Args> class ObjectType = std::map, + template<typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, + class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template<typename U> class AllocatorType = std::allocator, + template<typename T, typename SFINAE = void> class JSONSerializer = adl_serializer, + class BinaryType = std::vector<std::uint8_t>, + class CustomBaseClass = void +> +class basic_json; +``` + +The default specialization is the convenient type alias: + +```cpp +using json = basic_json<>; +``` + +An insertion-order-preserving variant is also provided: + +```cpp +using ordered_json = basic_json<nlohmann::ordered_map>; +``` + +## Key Features + +### Header-Only Design + +The entire library ships in a single header (`single_include/nlohmann/json.hpp`) +or as a multi-header tree rooted at `include/nlohmann/json.hpp`. No compilation +of library code is needed — just `#include` and use. + +The multi-header layout, used when `JSON_MultipleHeaders` is ON in CMake, +breaks the implementation into focused files under `include/nlohmann/detail/`: + +| Directory / File | Purpose | +|---|---| +| `detail/value_t.hpp` | `value_t` enumeration of JSON types | +| `detail/exceptions.hpp` | Exception hierarchy (`parse_error`, `type_error`, etc.) | +| `detail/json_pointer.hpp` | RFC 6901 JSON Pointer | +| `detail/input/lexer.hpp` | Tokenizer / lexical analyzer | +| `detail/input/parser.hpp` | Recursive-descent parser | +| `detail/input/json_sax.hpp` | SAX interface and DOM builders | +| `detail/input/binary_reader.hpp` | CBOR / MessagePack / UBJSON / BSON / BJData reader | +| `detail/input/input_adapters.hpp` | Input source abstraction (file, stream, string, iterators) | +| `detail/output/serializer.hpp` | JSON text serializer with UTF-8 validation | +| `detail/output/binary_writer.hpp` | Binary format writers | +| `detail/output/output_adapters.hpp` | Output sink abstraction | +| `detail/iterators/iter_impl.hpp` | Iterator implementation | +| `detail/iterators/iteration_proxy.hpp` | `items()` proxy for key-value iteration | +| `detail/conversions/from_json.hpp` | Default `from_json()` overloads | +| `detail/conversions/to_json.hpp` | Default `to_json()` overloads | +| `detail/macro_scope.hpp` | Configuration macros, `NLOHMANN_DEFINE_TYPE_*` | +| `detail/meta/type_traits.hpp` | SFINAE helpers and concept checks | + +### Intuitive Syntax + +```cpp +#include <nlohmann/json.hpp> +using json = nlohmann::json; + +// Create a JSON object +json j = { + {"name", "Project-Tick"}, + {"version", 3}, + {"features", {"parsing", "serialization", "patch"}}, + {"active", true} +}; + +// Access values +std::string name = j["name"]; +int version = j.at("version"); + +// Iterate +for (auto& [key, val] : j.items()) { + std::cout << key << ": " << val << "\n"; +} + +// Serialize +std::string pretty = j.dump(4); +``` + +### STL Container Compatibility + +`basic_json` models an STL container — it defines the standard type aliases +and fulfills the Container concept requirements: + +```cpp +// Container type aliases defined by basic_json: +using value_type = basic_json; +using reference = value_type&; +using const_reference = const value_type&; +using difference_type = std::ptrdiff_t; +using size_type = std::size_t; +using allocator_type = AllocatorType<basic_json>; +using pointer = typename std::allocator_traits<allocator_type>::pointer; +using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; +using iterator = iter_impl<basic_json>; +using const_iterator = iter_impl<const basic_json>; +using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; +using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; +``` + +This means `basic_json` works with STL algorithms: + +```cpp +json arr = {3, 1, 4, 1, 5}; +std::sort(arr.begin(), arr.end()); +auto it = std::find(arr.begin(), arr.end(), 4); +``` + +### Implicit Type Conversions + +By default (`JSON_USE_IMPLICIT_CONVERSIONS=1`), values can be implicitly +converted to native C++ types: + +```cpp +json j = 42; +int x = j; // implicit conversion +std::string s = j; // throws type_error::302 — type mismatch +``` + +This can be disabled at compile time with `-DJSON_ImplicitConversions=OFF` +(sets `JSON_USE_IMPLICIT_CONVERSIONS` to 0), requiring explicit `.get<T>()` +calls instead. + +### Comprehensive JSON Value Types + +Every JSON value type maps to a C++ type through the `value_t` enumeration +defined in `detail/value_t.hpp`: + +| JSON Type | `value_t` Enumerator | C++ Storage Type | Default | +|---|---|---|---| +| Object | `value_t::object` | `object_t*` | `std::map<std::string, basic_json>` | +| Array | `value_t::array` | `array_t*` | `std::vector<basic_json>` | +| String | `value_t::string` | `string_t*` | `std::string` | +| Boolean | `value_t::boolean` | `boolean_t` | `bool` | +| Integer | `value_t::number_integer` | `number_integer_t` | `std::int64_t` | +| Unsigned | `value_t::number_unsigned` | `number_unsigned_t` | `std::uint64_t` | +| Float | `value_t::number_float` | `number_float_t` | `double` | +| Binary | `value_t::binary` | `binary_t*` | `byte_container_with_subtype<vector<uint8_t>>` | +| Null | `value_t::null` | (none) | — | +| Discarded | `value_t::discarded` | (none) | — | + +Variable-length types (object, array, string, binary) are stored as heap +pointers to keep the `json_value` union at 8 bytes on 64-bit platforms. + +### Binary Format Support + +Beyond JSON text, the library supports round-trip conversion to and from +several binary serialization formats: + +- **CBOR** (RFC 7049) — `to_cbor()` / `from_cbor()` +- **MessagePack** — `to_msgpack()` / `from_msgpack()` +- **UBJSON** — `to_ubjson()` / `from_ubjson()` +- **BSON** (MongoDB) — `to_bson()` / `from_bson()` +- **BJData** — `to_bjdata()` / `from_bjdata()` + +### RFC Compliance + +| Feature | Specification | +|---|---| +| JSON Pointer | RFC 6901 — navigating JSON documents with path syntax | +| JSON Patch | RFC 6902 — describing mutations as operation arrays | +| JSON Merge Patch | RFC 7396 — simplified document merging | + +### SAX-Style Parsing + +For memory-constrained scenarios or streaming, the SAX interface +(`json_sax<BasicJsonType>`) allows event-driven parsing without building +a DOM tree in memory. + +### Custom Type Serialization + +The ADL-based serializer architecture lets users define `to_json()` and +`from_json()` free functions for any user-defined type. Convenience macros +automate this: + +- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, member1, member2, ...)` +- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, member1, member2, ...)` +- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...)` +- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)` +- `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...)` +- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...)` +- `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...)` + +### User-Defined Literals + +When `JSON_USE_GLOBAL_UDLS` is enabled (the default), two string literals +are available globally: + +```cpp +auto j = R"({"key": "value"})"_json; +auto ptr = "/key"_json_pointer; +``` + +These are always available as `nlohmann::literals::json_literals::operator""_json` +and `operator""_json_pointer`. + +## Version Information + +The library provides compile-time version macros: + +```cpp +NLOHMANN_JSON_VERSION_MAJOR // 3 +NLOHMANN_JSON_VERSION_MINOR // 12 +NLOHMANN_JSON_VERSION_PATCH // 0 +``` + +And a runtime introspection method: + +```cpp +json meta = json::meta(); +// Returns: +// { +// "copyright": "(C) 2013-2026 Niels Lohmann", +// "name": "JSON for Modern C++", +// "url": "https://github.com/nlohmann/json", +// "version": {"string": "3.12.0", "major": 3, "minor": 12, "patch": 0}, +// "compiler": {...}, +// "platform": "linux" +// } +``` + +## Compiler Support + +The library requires C++11 at minimum. Higher standard modes unlock +additional features: + +| Standard | Features Enabled | +|---|---| +| C++11 | Full library functionality | +| C++14 | `constexpr` support for `get<>()`, transparent comparators (`std::less<>`) | +| C++17 | `std::string_view` support, `std::any` integration, `if constexpr` | +| C++20 | Three-way comparison (`<=>` / `std::partial_ordering`), `std::format` | + +Automatic detection uses `__cplusplus` (or `_MSVC_LANG` on MSVC) and defines: + +- `JSON_HAS_CPP_11` — always 1 +- `JSON_HAS_CPP_14` — C++14 or above +- `JSON_HAS_CPP_17` — C++17 or above +- `JSON_HAS_CPP_20` — C++20 or above + +## Configuration Macros + +The library's behavior is controlled by preprocessor macros, typically set +via CMake options: + +| Macro | CMake Option | Default | Effect | +|---|---|---|---| +| `JSON_DIAGNOSTICS` | `JSON_Diagnostics` | `OFF` | Extended diagnostic messages with parent paths | +| `JSON_DIAGNOSTIC_POSITIONS` | `JSON_Diagnostic_Positions` | `OFF` | Track byte positions in parsed values | +| `JSON_USE_IMPLICIT_CONVERSIONS` | `JSON_ImplicitConversions` | `ON` | Allow implicit `operator ValueType()` | +| `JSON_DISABLE_ENUM_SERIALIZATION` | `JSON_DisableEnumSerialization` | `OFF` | Disable automatic enum-to-int conversion | +| `JSON_USE_GLOBAL_UDLS` | `JSON_GlobalUDLs` | `ON` | Place UDLs in global namespace | +| `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON` | `JSON_LegacyDiscardedValueComparison` | `OFF` | Old comparison behavior for discarded values | +| `JSON_NO_IO` | — | (not set) | Disable `<iosfwd>` / stream operators | + +## License + +nlohmann/json is released under the **MIT License**. + +``` +SPDX-License-Identifier: MIT +SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> +``` + +## Directory Structure in Project-Tick + +``` +json4cpp/ +├── CMakeLists.txt # Top-level build configuration +├── include/nlohmann/ # Multi-header source tree +│ ├── json.hpp # Main header +│ ├── json_fwd.hpp # Forward declarations +│ ├── adl_serializer.hpp # ADL serializer +│ ├── byte_container_with_subtype.hpp +│ ├── ordered_map.hpp # Insertion-order map +│ └── detail/ # Implementation details +├── single_include/nlohmann/ +│ ├── json.hpp # Amalgamated single header +│ └── json_fwd.hpp # Forward declarations +├── tests/ # Doctest-based test suite +├── docs/ # Upstream documentation source +├── tools/ # Code generation and maintenance scripts +├── cmake/ # CMake modules and configs +├── BUILD.bazel # Bazel build file +├── MODULE.bazel # Bazel module definition +├── Package.swift # Swift Package Manager support +├── meson.build # Meson build file +└── Makefile # Convenience Makefile +``` + +## Further Reading + +The remaining handbook documents cover: + +- **architecture.md** — Internal class hierarchy and template structure +- **building.md** — Integration methods, CMake support, compilation options +- **basic-usage.md** — Creating JSON values, accessing data, type system +- **value-types.md** — All JSON value types and their C++ representation +- **element-access.md** — `operator[]`, `at()`, `value()`, `find()`, `contains()` +- **iteration.md** — Iterators, range-for, `items()`, structured bindings +- **serialization.md** — `dump()`, `parse()`, stream I/O, `to_json`/`from_json` +- **binary-formats.md** — MessagePack, CBOR, BSON, UBJSON, BJData +- **json-pointer.md** — RFC 6901 JSON Pointer navigation +- **json-patch.md** — RFC 6902 JSON Patch and RFC 7396 Merge Patch +- **custom-types.md** — ADL serialization and `NLOHMANN_DEFINE_TYPE_*` macros +- **parsing-internals.md** — Lexer, parser, and SAX DOM builder internals +- **exception-handling.md** — Exception types, error IDs, when they are thrown +- **sax-interface.md** — SAX-style event-driven parsing +- **performance.md** — Performance characteristics and tuning +- **code-style.md** — Source code conventions +- **testing.md** — Test framework and running tests diff --git a/docs/handbook/json4cpp/parsing-internals.md b/docs/handbook/json4cpp/parsing-internals.md new file mode 100644 index 0000000000..ecbc946dee --- /dev/null +++ b/docs/handbook/json4cpp/parsing-internals.md @@ -0,0 +1,493 @@ +# json4cpp — Parsing Internals + +## Parser Architecture + +The parsing pipeline consists of three stages: + +``` +Input → InputAdapter → Lexer → Parser → JSON value + ↓ + SAX Handler +``` + +1. **Input adapters** normalize various input sources into a uniform byte stream +2. **Lexer** tokenizes the byte stream into JSON tokens +3. **Parser** implements a recursive descent parser driven by SAX events + +## Input Adapters + +Defined in `include/nlohmann/detail/input/input_adapters.hpp`. + +### Adapter Hierarchy + +```cpp +// File input +class file_input_adapter { + std::FILE* m_file; + std::char_traits<char>::int_type get_character(); +}; + +// Stream input +class input_stream_adapter { + std::istream* is; + std::streambuf* sb; + std::char_traits<char>::int_type get_character(); +}; + +// Iterator-based input +template<typename IteratorType> +class iterator_input_adapter { + IteratorType current; + IteratorType end; + std::char_traits<char>::int_type get_character(); +}; +``` + +All adapters expose a `get_character()` method that returns the next byte +or `std::char_traits<char>::eof()` at end of input. + +### `input_adapter()` Factory + +The free function `input_adapter()` selects the appropriate adapter: + +```cpp +// From string/string_view +auto adapter = input_adapter(std::string("{}")); + +// From iterators +auto adapter = input_adapter(vec.begin(), vec.end()); + +// From stream +auto adapter = input_adapter(std::cin); +``` + +### Span Input Adapter + +For contiguous memory (C++17): + +```cpp +template<typename CharT> +class contiguous_bytes_input_adapter { + const CharT* current; + const CharT* end; +}; +``` + +This is the fastest adapter since it reads directly from memory without +virtual dispatch. + +## Lexer + +Defined in `include/nlohmann/detail/input/lexer.hpp`. The lexer +(scanner/tokenizer) converts a byte stream into a sequence of tokens. + +### Token Types + +```cpp +enum class token_type +{ + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the 'true' literal + literal_false, ///< the 'false' literal + literal_null, ///< the 'null' literal + value_string, ///< a string (includes the quotes) + value_unsigned, ///< an unsigned integer + value_integer, ///< a signed integer + value_float, ///< a floating-point number + begin_array, ///< the character '[' + begin_object, ///< the character '{' + end_array, ///< the character ']' + end_object, ///< the character '}' + name_separator, ///< the character ':' + value_separator, ///< the character ',' + parse_error, ///< indicating a parse error + end_of_input ///< indicating the end of the input buffer +}; +``` + +### Lexer Class + +```cpp +template<typename BasicJsonType, typename InputAdapterType> +class lexer : public lexer_base<BasicJsonType> +{ +public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + + // Main scanning entry point + token_type scan(); + + // Access scanned values + constexpr number_integer_t get_number_integer() const noexcept; + constexpr number_unsigned_t get_number_unsigned() const noexcept; + constexpr number_float_t get_number_float() const noexcept; + string_t& get_string(); + + // Error information + constexpr position_t get_position() const noexcept; + std::string get_token_string() const; + const std::string& get_error_message() const noexcept; + +private: + InputAdapterType ia; // input source + char_int_type current; // current character + bool next_unget = false; // lookahead flag + position_t position {}; // line/column tracking + std::vector<char_type> token_string {}; // raw token for error messages + string_t token_buffer {}; // decoded string value + // Number storage (only one is valid at a time) + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; +}; +``` + +### Position Tracking + +```cpp +struct position_t +{ + std::size_t chars_read_total = 0; // total characters read + std::size_t chars_read_current_line = 0; // characters on current line + std::size_t lines_read = 0; // lines read (newline count) +}; +``` + +### String Scanning + +The `scan_string()` method handles: +- Regular characters +- Escape sequences: `\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t` +- Unicode escapes: `\uXXXX` (including surrogate pairs for `\uD800`–`\uDBFF` + `\uDC00`–`\uDFFF`) +- UTF-8 validation using a state machine + +### Number Scanning + +The `scan_number()` method determines the number type: + +1. Parse sign (optional `-`) +2. Parse integer part +3. If `.` follows → parse fractional part → `value_float` +4. If `e`/`E` follows → parse exponent → `value_float` +5. Otherwise, try to fit into `number_integer_t` or `number_unsigned_t` + +The method first accumulates the raw characters, then converts: +- Integers: `std::strtoull` / `std::strtoll` +- Floats: `std::strtod` + +### Comment Scanning + +When `ignore_comments` is enabled: + +```cpp +bool scan_comment() { + // After seeing '/', check next char: + // '/' → scan to end of line (C++ comment) + // '*' → scan to '*/' (C comment) +} +``` + +## Parser + +Defined in `include/nlohmann/detail/input/parser.hpp`. Implements a +**recursive descent** parser that generates SAX events. + +### Parser Class + +```cpp +template<typename BasicJsonType, typename InputAdapterType> +class parser +{ +public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer<BasicJsonType, InputAdapterType>; + + parser(InputAdapterType&& adapter, + const parser_callback_t<BasicJsonType> cb = nullptr, + const bool allow_exceptions_ = true, + const bool skip_comments = false, + const bool ignore_trailing_commas_ = false); + + void parse(const bool strict, BasicJsonType& result); + bool accept(const bool strict = true); + + template<typename SAX> + bool sax_parse(SAX* sax, const bool strict = true); + +private: + template<typename SAX> + bool sax_parse_internal(SAX* sax); + + lexer_t m_lexer; + token_type last_token = token_type::uninitialized; + bool allow_exceptions; + bool ignore_trailing_commas; +}; +``` + +### Recursive Descent Grammar + +The parser implements the JSON grammar: + +``` +json → value +value → object | array | string | number | "true" | "false" | "null" +object → '{' (pair (',' pair)* ','?)? '}' +pair → string ':' value +array → '[' (value (',' value)* ','?)? ']' +``` + +The trailing comma handling is optional (controlled by +`ignore_trailing_commas`). + +### SAX-Driven Parsing + +The parser calls SAX handler methods as it encounters JSON structure: + +```cpp +template<typename SAX> +bool sax_parse_internal(SAX* sax) +{ + switch (last_token) { + case token_type::begin_object: + // 1. sax->start_object(...) + // 2. For each key-value: + // a. sax->key(string) + // b. recurse into sax_parse_internal for value + // 3. sax->end_object() + break; + case token_type::begin_array: + // 1. sax->start_array(...) + // 2. For each element: recurse into sax_parse_internal + // 3. sax->end_array() + break; + case token_type::value_string: + return sax->string(m_lexer.get_string()); + case token_type::value_unsigned: + return sax->number_unsigned(m_lexer.get_number_unsigned()); + case token_type::value_integer: + return sax->number_integer(m_lexer.get_number_integer()); + case token_type::value_float: + // Check for NaN: store as null + return sax->number_float(m_lexer.get_number_float(), ...); + case token_type::literal_true: + return sax->boolean(true); + case token_type::literal_false: + return sax->boolean(false); + case token_type::literal_null: + return sax->null(); + default: + return sax->parse_error(...); + } +} +``` + +### DOM Construction + +Two SAX handlers build the DOM tree: + +#### `json_sax_dom_parser` + +Standard DOM builder. Each SAX event creates or appends to the JSON tree: + +```cpp +template<typename BasicJsonType> +class json_sax_dom_parser +{ + BasicJsonType& root; + std::vector<BasicJsonType*> ref_stack; // stack of parent nodes + BasicJsonType* object_element = nullptr; + bool errored = false; + bool allow_exceptions; + + 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 detail::exception& ex); +}; +``` + +The `ref_stack` tracks the current nesting path. On `start_object()` / +`start_array()`, a new container is pushed. On `end_object()` / +`end_array()`, the stack is popped. + +#### `json_sax_dom_callback_parser` + +Extends the DOM builder with callback support. When the callback returns +`false`, the value is discarded: + +```cpp +template<typename BasicJsonType> +class json_sax_dom_callback_parser +{ + BasicJsonType& root; + std::vector<BasicJsonType*> ref_stack; + std::vector<bool> keep_stack; // tracks which values to keep + std::vector<bool> key_keep_stack; + BasicJsonType* object_element = nullptr; + BasicJsonType discarded = BasicJsonType::value_t::discarded; + parser_callback_t<BasicJsonType> callback; + bool errored = false; + bool allow_exceptions; +}; +``` + +## `accept()` Method + +The `accept()` method checks validity without building a DOM: + +```cpp +bool accept(const bool strict = true); +``` + +Internally it uses `json_sax_acceptor` — a SAX handler where all methods +return `true` (accepting everything) and `parse_error()` returns `false`: + +```cpp +template<typename BasicJsonType> +struct json_sax_acceptor +{ + bool null() { return true; } + bool boolean(bool) { return true; } + bool number_integer(number_integer_t) { return true; } + // ... all return true ... + bool parse_error(...) { return false; } +}; +``` + +## `sax_parse()` — Static SAX Entry Point + +```cpp +template<typename InputType, typename SAX> +static bool sax_parse(InputType&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false); +``` + +The `input_format_t` enum selects the parser: + +```cpp +enum class input_format_t { + json, + cbor, + msgpack, + ubjson, + bson, + bjdata +}; +``` + +For `json`, the text parser is used. For binary formats, the +`binary_reader` is used (which also generates SAX events). + +## Error Reporting + +### Parse Error Format + +``` +[json.exception.parse_error.101] parse error at line 3, column 5: +syntax error while parsing object key - unexpected end of input; +expected string literal +``` + +The error message includes: +- Exception ID (e.g., 101) +- Position (line and column, or byte offset) +- Description of what was expected vs. what was found +- The last token read (for context) + +### Error IDs + +| ID | Condition | +|---|---| +| 101 | Unexpected token | +| 102 | `\u` escape with invalid hex digits | +| 103 | Invalid UTF-8 surrogate pair | +| 104 | JSON Patch: invalid patch document | +| 105 | JSON Patch: missing required field | +| 106 | Invalid number format | +| 107 | Invalid JSON Pointer syntax | +| 108 | Invalid Unicode code point | +| 109 | Invalid UTF-8 byte sequence | +| 110 | Unrecognized CBOR/MessagePack/UBJSON/BSON marker | +| 112 | Parse error in BSON | +| 113 | Parse error in UBJSON | +| 114 | Parse error in BJData | +| 115 | Parse error due to incomplete binary data | + +### Diagnostic Positions + +When `JSON_DIAGNOSTIC_POSITIONS` is enabled at compile time, the library +tracks byte positions for each value. Error messages then include +`start_position` and `end_position` for the offending value: + +```cpp +#define JSON_DIAGNOSTICS 1 +#define JSON_DIAGNOSTIC_POSITIONS 1 +``` + +## Parser Callback Events + +The parser callback receives events defined by `parse_event_t`: + +```cpp +enum class parse_event_t : std::uint8_t +{ + object_start, // '{' read + object_end, // '}' read + array_start, // '[' read + array_end, // ']' read + key, // object key read + value // value read +}; +``` + +Callback invocation points in the parser: +1. `object_start` — after `{` is consumed, before any key +2. `key` — after a key string is consumed, `parsed` = the key string +3. `value` — after any value is fully parsed, `parsed` = the value +4. `object_end` — after `}` is consumed, `parsed` = the complete object +5. `array_start` — after `[` is consumed, before any element +6. `array_end` — after `]` is consumed, `parsed` = the complete array + +### Callback Return Value + +- `true` → keep the value +- `false` → discard (replace with `discarded`) + +For container events (`object_start`, `array_start`), returning `false` +skips the **entire** container and all its contents. + +## Performance Characteristics + +| Stage | Complexity | Dominant Cost | +|---|---|---| +| Input adapter | O(n) | Single pass over input | +| Lexer | O(n) | Character-by-character scan, string copy | +| Parser | O(n) | Recursive descent, SAX event dispatch | +| DOM construction | O(n) | Memory allocation for containers | + +The overall parsing complexity is O(n) in the input size. Memory usage is +proportional to the nesting depth (parser stack) plus the size of the +resulting DOM (heap allocations for strings, arrays, objects). + +For large inputs where the full DOM is not needed, using the SAX interface +directly avoids DOM construction overhead entirely. diff --git a/docs/handbook/json4cpp/performance.md b/docs/handbook/json4cpp/performance.md new file mode 100644 index 0000000000..a35d0bc4b8 --- /dev/null +++ b/docs/handbook/json4cpp/performance.md @@ -0,0 +1,275 @@ +# json4cpp — Performance + +## Memory Layout + +### `json_value` Union + +The core storage is a union of 8 members: + +```cpp +union json_value +{ + object_t* object; // 8 bytes (pointer) + array_t* array; // 8 bytes (pointer) + string_t* string; // 8 bytes (pointer) + binary_t* binary; // 8 bytes (pointer) + boolean_t boolean; // 1 byte + number_integer_t number_integer; // 8 bytes + number_unsigned_t number_unsigned; // 8 bytes + number_float_t number_float; // 8 bytes +}; +``` + +The union is **8 bytes** on 64-bit platforms. Variable-length types +(object, array, string, binary) are stored as heap-allocated pointers to +keep the union small. + +### Total `basic_json` Size + +Each `basic_json` node contains: + +```cpp +struct data +{ + value_t m_type = value_t::null; // 1 byte (uint8_t enum) + // + padding + json_value m_value = {}; // 8 bytes +}; +``` + +With alignment: **16 bytes per node** on most 64-bit platforms (1 byte +type + 7 bytes padding + 8 bytes value). + +When `JSON_DIAGNOSTICS` is enabled, each node additionally stores a parent +pointer: + +```cpp +struct data +{ + value_t m_type; + json_value m_value; + const basic_json* m_parent = nullptr; // 8 bytes extra +}; +``` + +Total with diagnostics: **24 bytes per node**. + +## Allocation Strategy + +### Object Storage (default `std::map`) + +- Red-black tree nodes: ~48–64 bytes each (key + value + pointers + color) +- O(log n) lookup, insert, erase +- Good cache locality within individual nodes, poor across the tree + +### Array Storage (`std::vector`) + +- Contiguous memory: amortized O(1) push_back +- Reallocations: capacity doubles, causing copies of all elements +- Each element is 16 bytes (`basic_json`) + +### String Storage (`std::string`) + +- SSO (Small String Optimization): strings ≤ ~15 chars stored inline + (no allocation). Exact threshold is implementation-defined. +- Longer strings: heap allocation + +## `ordered_map` Performance + +`ordered_json` uses `ordered_map<std::string, basic_json>` which inherits +from `std::vector<std::pair<const Key, T>>`: + +| Operation | `std::map` (json) | `ordered_map` (ordered_json) | +|---|---|---| +| Lookup by key | O(log n) | O(n) linear search | +| Insert | O(log n) | O(1) amortized (push_back) | +| Erase by key | O(log n) | O(n) (shift elements) | +| Iteration | O(n), sorted order | O(n), insertion order | +| Memory | Tree nodes (fragmented) | Contiguous vector | + +Use `ordered_json` only when insertion order matters and the number of +keys is small (< ~100). + +## Destruction + +### Iterative Destruction + +Deeply nested JSON values would cause stack overflow with recursive +destructors. The library uses **iterative destruction**: + +```cpp +void data::destroy(value_t t) +{ + if (t == value_t::array || t == value_t::object) + { + // Move children to a flat list + std::vector<basic_json> stack; + if (t == value_t::array) { + stack.reserve(m_value.array->size()); + std::move(m_value.array->begin(), m_value.array->end(), + std::back_inserter(stack)); + } else { + // Extract values from object pairs + for (auto& pair : *m_value.object) { + stack.push_back(std::move(pair.second)); + } + } + // Continue flattening until stack is empty + while (!stack.empty()) { + // Pop and flatten nested containers + } + } + // Destroy the container itself +} +``` + +This ensures O(1) stack depth regardless of JSON nesting depth. + +### Destruction Cost + +- Primitives (null, boolean, number): O(1), no heap deallocation +- String: O(1), single `delete` +- Array: O(n), iterative flattening + deallocation of each element +- Object: O(n), iterative flattening + deallocation of each key-value +- Binary: O(1), single `delete` + +## Parsing Performance + +### Lexer Optimizations + +- Single-character lookahead (no backtracking) +- Token string is accumulated in a pre-allocated buffer +- Number parsing avoids `std::string` intermediate: raw chars → integer or + float directly via `strtoull`/`strtod` +- UTF-8 validation uses a compact state machine (400-byte lookup table) + +### Parser Complexity + +- O(n) in input size +- O(d) stack depth where d = maximum nesting depth +- SAX approach avoids intermediate DOM allocations + +### Fastest Parsing Path + +For maximum speed: +1. Use contiguous input (`std::string`, `const char*`, `std::vector<uint8_t>`) + — avoids virtual dispatch in input adapter +2. Disable comments (`ignore_comments = false`) +3. Disable trailing commas (`ignore_trailing_commas = false`) +4. No callback (`cb = nullptr`) +5. Allow exceptions (`allow_exceptions = true`) — avoids extra bookkeeping + +## Serialization Performance + +### Number Formatting + +- **Integers**: Custom digit-by-digit algorithm writing to a 64-byte stack + buffer. Faster than `std::to_string` (no `std::string` allocation). +- **Floats**: `std::snprintf` with `max_digits10` precision. The format + string is `%.*g`. + +### String Escaping + +- ASCII-only strings: nearly zero overhead (copy + quote wrapping) +- Strings with special characters: per-byte check against escape table +- `ensure_ascii`: full UTF-8 decode + `\uXXXX` encoding (slower) + +### Output Adapter + +- `output_string_adapter` (default for `dump()`): writes to `std::string` + with `push_back()` / `append()` +- `output_stream_adapter`: writes to `std::ostream` via `put()` / `write()` +- `output_vector_adapter`: writes to `std::vector<char>` via `push_back()` + +## Compilation Time + +Being header-only, json.hpp can add significant compilation time. Strategies: + +### Single Include vs. Multi-Header + +| Approach | Files | Compilation Model | +|---|---|---| +| `single_include/nlohmann/json.hpp` | 1 file (~25K lines) | Include everywhere | +| `include/nlohmann/json.hpp` | Many small headers | Better incremental builds | + +### Reducing Compilation Time + +1. **Precompiled headers**: Add `nlohmann/json.hpp` to your PCH +2. **Forward declarations**: Use `nlohmann/json_fwd.hpp` in headers, full + include only in `.cpp` files +3. **Extern template**: Pre-instantiate in one TU: + +```cpp +// json_instantiation.cpp +#include <nlohmann/json.hpp> +template class nlohmann::basic_json<>; // explicit instantiation +``` + +4. **Minimize includes**: Only include where actually needed + +## Binary Format Performance + +Size and speed characteristics compared to JSON text: + +| Aspect | JSON Text | CBOR | MessagePack | UBJSON | +|---|---|---|---|---| +| Encoding speed | Fast | Fast | Fast | Moderate | +| Decoding speed | Moderate | Fast | Fast | Moderate | +| Output size | Largest | Compact | Most compact | Moderate | +| Human readable | Yes | No | No | No | + +Binary formats are generally faster to parse because: +- No string-to-number conversion (numbers stored in binary) +- Size-prefixed containers (no scanning for delimiters) +- No whitespace handling +- No string escape processing + +## Best Practices + +### Avoid Copies + +```cpp +// Bad: copies the entire array +json arr = j["data"]; + +// Good: reference +const auto& arr = j["data"]; +``` + +### Use `get_ref()` for String Access + +```cpp +// Bad: copies the string +std::string s = j.get<std::string>(); + +// Good: reference (no copy) +const auto& s = j.get_ref<const std::string&>(); +``` + +### Reserve Capacity + +```cpp +json j = json::array(); +// If you know the size, reserve first (via underlying container) +// The API doesn't expose reserve() directly, but you can: +json j = json::parse(input); // parser pre-allocates when size hints are available +``` + +### SAX for Large Documents + +```cpp +// Bad: loads entire 1GB file into DOM +json j = json::parse(huge_file); + +// Good: process streaming with SAX +struct my_handler : nlohmann::json_sax<json> { /* ... */ }; +my_handler handler; +json::sax_parse(huge_file, &handler); +``` + +### Move Semantics + +```cpp +json source = get_data(); +json dest = std::move(source); // O(1) move, source becomes null +``` diff --git a/docs/handbook/json4cpp/sax-interface.md b/docs/handbook/json4cpp/sax-interface.md new file mode 100644 index 0000000000..3164be9694 --- /dev/null +++ b/docs/handbook/json4cpp/sax-interface.md @@ -0,0 +1,337 @@ +# json4cpp — SAX Interface + +## Overview + +The SAX (Simple API for XML/JSON) interface provides an event-driven +parsing model. Instead of building a complete DOM tree in memory, the +parser reports structural events to a handler as it reads the input. + +This is useful for: +- Processing very large JSON documents without loading them fully +- Filtering or transforming data during parsing +- Building custom data structures directly from JSON input +- Reducing memory usage + +## `json_sax` Abstract Class + +Defined in `include/nlohmann/detail/input/json_sax.hpp`: + +```cpp +template<typename BasicJsonType> +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + virtual bool null() = 0; + virtual bool boolean(bool val) = 0; + virtual bool number_integer(number_integer_t val) = 0; + virtual bool number_unsigned(number_unsigned_t val) = 0; + virtual bool number_float(number_float_t val, const string_t& s) = 0; + virtual bool string(string_t& val) = 0; + virtual bool binary(binary_t& val) = 0; + + virtual bool start_object(std::size_t elements) = 0; + virtual bool key(string_t& val) = 0; + virtual bool end_object() = 0; + + virtual bool start_array(std::size_t elements) = 0; + virtual bool end_array() = 0; + + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; +``` + +## Event Methods + +### Scalar Events + +| Method | Called When | Arguments | +|---|---|---| +| `null()` | `null` literal parsed | — | +| `boolean(val)` | `true` or `false` parsed | `bool val` | +| `number_integer(val)` | Signed integer parsed | `number_integer_t val` | +| `number_unsigned(val)` | Unsigned integer parsed | `number_unsigned_t val` | +| `number_float(val, s)` | Float parsed | `number_float_t val`, `string_t s` (raw text) | +| `string(val)` | String parsed | `string_t& val` | +| `binary(val)` | Binary data parsed | `binary_t& val` | + +### Container Events + +| Method | Called When | Arguments | +|---|---|---| +| `start_object(n)` | `{` read | Element count hint (or -1 if unknown) | +| `key(val)` | Object key read | `string_t& val` | +| `end_object()` | `}` read | — | +| `start_array(n)` | `[` read | Element count hint (or -1 if unknown) | +| `end_array()` | `]` read | — | + +### Error Event + +| Method | Called When | Arguments | +|---|---|---| +| `parse_error(pos, tok, ex)` | Parse error | Byte position, last token, exception | + +### Return Values + +All methods return `bool`: +- `true` — continue parsing +- `false` — abort parsing immediately + +For `parse_error()`: +- `true` — abort with no exception (return discarded value) +- `false` — abort with no exception (return discarded value) + +In practice, the return value of `parse_error()` has the same effect +regardless — the parser stops. The distinction matters for whether +exceptions are thrown (controlled by `allow_exceptions`). + +## Using `sax_parse()` + +```cpp +template<typename InputType, typename SAX> +static bool sax_parse(InputType&& i, + SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false); +``` + +```cpp +MySaxHandler handler; +bool success = json::sax_parse(input_string, &handler); +``` + +The `format` parameter supports binary formats too: + +```cpp +json::sax_parse(cbor_data, &handler, json::input_format_t::cbor); +json::sax_parse(msgpack_data, &handler, json::input_format_t::msgpack); +``` + +## Implementing a Custom SAX Handler + +### Minimal Handler (Count Elements) + +```cpp +struct counter_handler : nlohmann::json_sax<json> +{ + std::size_t values = 0; + std::size_t objects = 0; + std::size_t arrays = 0; + + bool null() override { ++values; return true; } + bool boolean(bool) override { ++values; return true; } + bool number_integer(json::number_integer_t) override { ++values; return true; } + bool number_unsigned(json::number_unsigned_t) override { ++values; return true; } + bool number_float(json::number_float_t, const std::string&) override { ++values; return true; } + bool string(std::string&) override { ++values; return true; } + bool binary(json::binary_t&) override { ++values; return true; } + + bool start_object(std::size_t) override { ++objects; return true; } + bool key(std::string&) override { return true; } + bool end_object() override { return true; } + + bool start_array(std::size_t) override { ++arrays; return true; } + bool end_array() override { return true; } + + bool parse_error(std::size_t, const std::string&, + const nlohmann::detail::exception&) override + { return false; } +}; + +// Usage +counter_handler handler; +json::sax_parse(R"({"a": [1, 2], "b": true})", &handler); +// handler.values == 3 (1, 2, true) +// handler.objects == 1 +// handler.arrays == 1 +``` + +### Key Extractor + +Extract all keys from a JSON document without building the DOM: + +```cpp +struct key_extractor : nlohmann::json_sax<json> +{ + std::vector<std::string> keys; + int depth = 0; + + bool null() override { return true; } + bool boolean(bool) override { return true; } + bool number_integer(json::number_integer_t) override { return true; } + bool number_unsigned(json::number_unsigned_t) override { return true; } + bool number_float(json::number_float_t, const std::string&) override { return true; } + bool string(std::string&) override { return true; } + bool binary(json::binary_t&) override { return true; } + + bool start_object(std::size_t) override { ++depth; return true; } + bool key(std::string& val) override { + keys.push_back(val); + return true; + } + bool end_object() override { --depth; return true; } + + bool start_array(std::size_t) override { return true; } + bool end_array() override { return true; } + + bool parse_error(std::size_t, const std::string&, + const nlohmann::detail::exception&) override + { return false; } +}; +``` + +### Early Termination + +Return `false` from any method to stop parsing immediately: + +```cpp +struct find_key_handler : nlohmann::json_sax<json> +{ + std::string target_key; + json found_value; + bool found = false; + bool capture_next = false; + + bool key(std::string& val) override { + capture_next = (val == target_key); + return true; + } + + bool string(std::string& val) override { + if (capture_next) { + found_value = val; + found = true; + return false; // stop parsing + } + return true; + } + + bool number_integer(json::number_integer_t val) override { + if (capture_next) { + found_value = val; + found = true; + return false; + } + return true; + } + + // ... remaining methods return true ... + bool null() override { return !capture_next || (found = true, false); } + bool boolean(bool v) override { + if (capture_next) { found_value = v; found = true; return false; } + return true; + } + bool number_unsigned(json::number_unsigned_t v) override { + if (capture_next) { found_value = v; found = true; return false; } + return true; + } + bool number_float(json::number_float_t v, const std::string&) override { + if (capture_next) { found_value = v; found = true; return false; } + return true; + } + bool binary(json::binary_t&) override { return true; } + bool start_object(std::size_t) override { capture_next = false; return true; } + bool end_object() override { return true; } + bool start_array(std::size_t) override { capture_next = false; return true; } + bool end_array() override { return true; } + bool parse_error(std::size_t, const std::string&, + const nlohmann::detail::exception&) override { return false; } +}; +``` + +## Built-in SAX Handlers + +### `json_sax_dom_parser` + +The default handler used by `parse()`. Builds a complete DOM tree: + +```cpp +template<typename BasicJsonType> +class json_sax_dom_parser +{ + BasicJsonType& root; + std::vector<BasicJsonType*> ref_stack; + BasicJsonType* object_element = nullptr; +}; +``` + +### `json_sax_dom_callback_parser` + +Used when `parse()` is called with a callback. Adds filtering logic: + +```cpp +template<typename BasicJsonType> +class json_sax_dom_callback_parser +{ + BasicJsonType& root; + std::vector<BasicJsonType*> ref_stack; + std::vector<bool> keep_stack; + std::vector<bool> key_keep_stack; + parser_callback_t<BasicJsonType> callback; +}; +``` + +### `json_sax_acceptor` + +Used by `accept()`. All event methods return `true`, `parse_error()` +returns `false`: + +```cpp +template<typename BasicJsonType> +struct json_sax_acceptor { + bool null() { return true; } + bool boolean(bool) { return true; } + // ... all true ... + bool parse_error(...) { return false; } +}; +``` + +## SAX with Binary Formats + +The SAX interface works uniformly across all supported formats. The +`binary_reader` generates the same SAX events from binary input: + +```cpp +struct my_handler : nlohmann::json_sax<json> { /* ... */ }; + +my_handler handler; + +// JSON text +json::sax_parse(json_text, &handler); + +// CBOR +json::sax_parse(cbor_bytes, &handler, json::input_format_t::cbor); + +// MessagePack +json::sax_parse(msgpack_bytes, &handler, json::input_format_t::msgpack); +``` + +The `start_object(n)` and `start_array(n)` methods receive the element +count as `n` for binary formats (where the count is known from the header). +For JSON text, `n` is always `static_cast<std::size_t>(-1)` (unknown). + +## Performance Considerations + +SAX parsing avoids DOM construction overhead: +- No heap allocations for JSON containers +- No recursive destruction of the DOM tree +- Constant memory usage (proportional to nesting depth only) +- Can process arbitrarily large documents + +For streaming scenarios where you need to process multiple JSON values +from a single input, use `sax_parse()` with `strict = false` in a loop. diff --git a/docs/handbook/json4cpp/serialization.md b/docs/handbook/json4cpp/serialization.md new file mode 100644 index 0000000000..56265d62f1 --- /dev/null +++ b/docs/handbook/json4cpp/serialization.md @@ -0,0 +1,528 @@ +# json4cpp — Serialization & Deserialization + +## Parsing (Deserialization) + +### `parse()` + +```cpp +template<typename InputType> +static basic_json parse(InputType&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false); +``` + +Parses JSON text from multiple source types. + +### Accepted Input Types + +| Input Type | Example | +|---|---| +| `std::string` / `string_view` | `json::parse("{}")` | +| `const char*` | `json::parse(ptr)` | +| `std::istream&` | `json::parse(file_stream)` | +| Iterator pair | `json::parse(vec.begin(), vec.end())` | +| `FILE*` | `json::parse(std::fopen("f.json", "r"))` | +| Contiguous container | `json::parse(std::vector<uint8_t>{...})` | + +### String Parsing + +```cpp +auto j = json::parse(R"({"key": "value", "num": 42})"); +``` + +### File Parsing + +```cpp +std::ifstream f("config.json"); +json j = json::parse(f); +``` + +### Iterator Parsing + +```cpp +std::string s = R"([1, 2, 3])"; +json j = json::parse(s.begin(), s.end()); + +std::vector<uint8_t> bytes = {'{', '}' }; +json j2 = json::parse(bytes); +``` + +### Error Handling + +By default, `parse()` throws `json::parse_error` on invalid input: + +```cpp +try { + json j = json::parse("not json"); +} catch (json::parse_error& e) { + std::cerr << e.what() << "\n"; + // "[json.exception.parse_error.101] parse error at line 1, column 1: + // syntax error while parsing value - invalid literal; ..." + std::cerr << "byte: " << e.byte << "\n"; // position of error +} +``` + +Set `allow_exceptions = false` to get a discarded value instead: + +```cpp +json j = json::parse("not json", nullptr, false); +assert(j.is_discarded()); +``` + +### Comments + +JSON does not support comments by standard, but the parser can skip them: + +```cpp +std::string input = R"({ + // line comment + "key": "value", + /* block comment */ + "num": 42 +})"; + +json j = json::parse(input, nullptr, true, true); // ignore_comments=true +``` + +Both C-style (`/* */`) and C++-style (`//`) comments are supported. + +### Trailing Commas + +```cpp +std::string input = R"({ + "a": 1, + "b": 2, +})"; + +json j = json::parse(input, nullptr, true, false, true); // ignore_trailing_commas=true +``` + +### `operator>>` + +Stream extraction operator: + +```cpp +std::istringstream ss(R"({"key": "value"})"); +json j; +ss >> j; +``` + +### `_json` User-Defined Literal + +```cpp +using namespace nlohmann::literals; + +auto j = R"({"key": "value"})"_json; +auto j2 = "[1, 2, 3]"_json; +``` + +The UDL is also available via `using namespace nlohmann::json_literals` or +`using namespace nlohmann::literals`. When `JSON_GlobalUDLs` is enabled +(the default), the literals are in the global namespace via +`inline namespace`. + +### `_json_pointer` Literal + +```cpp +using namespace nlohmann::literals; + +auto ptr = "/foo/bar/0"_json_pointer; +``` + +## Parser Callbacks + +### `parse_event_t` + +```cpp +enum class parse_event_t : std::uint8_t +{ + object_start, ///< the parser read `{` and started to process a JSON object + object_end, ///< the parser read `}` and finished processing a JSON object + array_start, ///< the parser read `[` and started to process a JSON array + array_end, ///< the parser read `]` and finished processing a JSON array + key, ///< the parser read a key of a value in an object + value ///< the parser finished reading a JSON value +}; +``` + +### Callback Signature + +```cpp +using parser_callback_t = std::function<bool(int depth, + parse_event_t event, + basic_json& parsed)>; +``` + +- `depth` — nesting depth (0 = top level) +- `event` — current parse event +- `parsed` — the parsed value (for `value`/`key` events) or null (for start/end) +- Return `true` to keep the value, `false` to discard + +### Filtering Example + +```cpp +// Remove all keys named "password" +json j = json::parse(input, [](int /*depth*/, json::parse_event_t event, json& parsed) { + if (event == json::parse_event_t::key && parsed == "password") { + return false; + } + return true; +}); +``` + +### Depth-Limited Parsing + +```cpp +// Only keep top-level keys +json j = json::parse(input, [](int depth, json::parse_event_t event, json&) { + if (depth > 1 && event == json::parse_event_t::value) { + return false; + } + return true; +}); +``` + +## Serialization + +### `dump()` + +```cpp +string_t dump(const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict) const; +``` + +Converts a JSON value to a string. + +### Compact Output + +```cpp +json j = {{"name", "alice"}, {"scores", {90, 85, 92}}}; +std::string s = j.dump(); +// {"name":"alice","scores":[90,85,92]} +``` + +### Pretty Printing + +```cpp +std::string s = j.dump(4); +// { +// "name": "alice", +// "scores": [ +// 90, +// 85, +// 92 +// ] +// } +``` + +You can change the indent character: + +```cpp +std::string s = j.dump(1, '\t'); +// { +// "name": "alice", +// "scores": [ +// 90, +// 85, +// 92 +// ] +// } +``` + +### ASCII Escaping + +When `ensure_ascii = true`, all non-ASCII characters are escaped: + +```cpp +json j = "München"; +j.dump(); // "München" +j.dump(-1, ' ', true); // "M\u00FCnchen" +``` + +### UTF-8 Error Handling + +The `error_handler` controls what happens when the serializer encounters +invalid UTF-8: + +```cpp +enum class error_handler_t +{ + strict, ///< throw type_error::316 on invalid UTF-8 + replace, ///< replace invalid bytes with U+FFFD + ignore ///< skip invalid bytes silently +}; +``` + +```cpp +// String with invalid UTF-8 byte 0xFF +std::string bad = "hello\xFFworld"; +json j = bad; + +j.dump(); // throws type_error::316 + +j.dump(-1, ' ', false, json::error_handler_t::replace); +// "hello\uFFFDworld" + +j.dump(-1, ' ', false, json::error_handler_t::ignore); +// "helloworld" +``` + +### `operator<<` + +Stream insertion operator for convenience: + +```cpp +json j = {{"key", "value"}}; +std::cout << j << "\n"; // compact: {"key":"value"} +std::cout << std::setw(4) << j; // pretty: 4-space indent +``` + +Using `std::setw()` with the stream sets the indentation level. + +## Serializer Internals + +The actual serialization is performed by `detail::serializer<basic_json>` +(in `include/nlohmann/detail/output/serializer.hpp`): + +```cpp +template<typename BasicJsonType> +class serializer +{ +public: + serializer(output_adapter_t<char> s, const char ichar, + error_handler_t error_handler_ = error_handler_t::strict); + + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, const unsigned int indent_step, + const unsigned int current_indent = 0); + +private: + void dump_escaped(const string_t& s, const bool ensure_ascii); + void dump_integer(number_integer_t x); + void dump_integer(number_unsigned_t x); + void dump_float(number_float_t x, std::true_type is_ieee); + void dump_float(number_float_t x, std::false_type is_ieee); +}; +``` + +### Number Serialization + +**Integers** are serialized using a custom digit-by-digit algorithm that +writes into a stack buffer (`number_buffer`), avoiding `std::to_string`: + +```cpp +// Internal buffer for number conversion +std::array<char, 64> number_buffer{{}}; +``` + +**Floating-point** values use different strategies: +- On IEEE 754 platforms (`std::true_type`): uses `std::snprintf` with + `%.*g` format, with precision from `std::numeric_limits<number_float_t>::max_digits10` +- On non-IEEE platforms (`std::false_type`): same `snprintf` approach + +Special float values: +- `NaN` → serialized as `null` +- `Infinity` → serialized as `null` + +### String Escaping + +The `dump_escaped()` method handles: +- Control characters (`\n`, `\t`, `\r`, `\b`, `\f`, `\\`, `\"`) +- Characters 0x00–0x1F are escaped as `\u00XX` +- Non-ASCII characters can be escaped as `\uXXXX` (if `ensure_ascii = true`) +- Surrogate pairs for characters above U+FFFF +- Invalid UTF-8 handling per `error_handler_t` + +The UTF-8 decoder uses a state machine: + +```cpp +static const std::array<std::uint8_t, 400> utf8d; +std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte); +``` + +States: `UTF8_ACCEPT` (0) and `UTF8_REJECT` (1). + +## Output Adapters + +Defined in `include/nlohmann/detail/output/output_adapters.hpp`: + +```cpp +template<typename CharType> +class output_adapter +{ +public: + // Adapts std::vector<CharType> + output_adapter(std::vector<CharType>& vec); + + // Adapts std::basic_ostream + output_adapter(std::basic_ostream<CharType>& s); + + // Adapts std::basic_string + output_adapter(StringType& s); +}; +``` + +Three concrete adapters: +- `output_vector_adapter` — writes to `std::vector<char>` +- `output_stream_adapter` — writes to `std::ostream` +- `output_string_adapter` — writes to `std::string` + +## ADL Serialization Mechanism + +### How `to_json()` / `from_json()` Work + +The library uses **Argument-Dependent Lookup (ADL)** to find conversion +functions. When you call `json j = my_obj;`, the library ultimately invokes: + +```cpp +nlohmann::adl_serializer<MyType>::to_json(j, my_obj); +``` + +The default `adl_serializer` delegates to a free function found via ADL: + +```cpp +template<typename ValueType, typename> +struct adl_serializer +{ + template<typename BasicJsonType, typename TargetType = ValueType> + static auto from_json(BasicJsonType&& j, TargetType& val) + -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void()) + { + ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); + } + + template<typename BasicJsonType, typename TargetType = ValueType> + static auto to_json(BasicJsonType& j, TargetType&& val) + -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void()) + { + ::nlohmann::to_json(j, std::forward<TargetType>(val)); + } +}; +``` + +### Built-in Conversions + +The library provides `to_json()` and `from_json()` overloads for: + +| C++ Type | JSON Type | +|---|---| +| `bool` | boolean | +| `int`, `long`, `int64_t`, etc. | number_integer | +| `unsigned`, `uint64_t`, etc. | number_unsigned | +| `float`, `double` | number_float | +| `std::string`, `const char*` | string | +| `std::nullptr_t` | null | +| `std::vector<T>`, `std::list<T>`, ... | array | +| `std::array<T, N>` | array | +| `std::map<string, T>` | object | +| `std::unordered_map<string, T>` | object | +| `std::pair<T1, T2>` | array of 2 | +| `std::tuple<Ts...>` | array of N | +| `std::optional<T>` (C++17) | value or null | +| `std::variant<Ts...>` (C++17) | depends on held type | +| Enum types | integer (unless disabled) | + +### Priority Tags for Overload Resolution + +Built-in `to_json()` overloads use a priority tag system to resolve +ambiguity: + +```cpp +template<typename BasicJsonType, typename T> +void to_json(BasicJsonType& j, T val, priority_tag<1>); // higher priority +template<typename BasicJsonType, typename T> +void to_json(BasicJsonType& j, T val, priority_tag<0>); // lower priority +``` + +The `priority_tag<N>` inherits from `priority_tag<N-1>`, so higher-tagged +overloads are preferred. + +## Roundtrip Guarantees + +### Integers + +Integer values survive a parse → dump → parse roundtrip exactly, as long +as they fit within `int64_t` or `uint64_t` range. + +### Floating-Point + +The library uses `max_digits10` precision (typically 17 for `double`) to +ensure roundtrip fidelity: + +```cpp +json j = 3.141592653589793; +std::string s = j.dump(); // "3.141592653589793" +json j2 = json::parse(s); +assert(j == j2); // true +``` + +### Strings + +UTF-8 strings roundtrip exactly. The serializer preserves all valid UTF-8 +sequences. Unicode escapes in input (`\uXXXX`) are converted to UTF-8 on +parsing and will be re-escaped only if `ensure_ascii = true`. + +## `accept()` + +```cpp +template<typename InputType> +static bool accept(InputType&& i, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false); +``` + +Checks whether the input is valid JSON without constructing a value: + +```cpp +json::accept("{}"); // true +json::accept("[1,2,3]"); // true +json::accept("not json"); // false +json::accept("{\"a\": 1,}"); // false +json::accept("{\"a\": 1,}", false, true); // true (trailing commas) +``` + +## Conversion to/from STL Types + +### Explicit Conversion (`get<T>()`) + +```cpp +json j = 42; +int i = j.get<int>(); +double d = j.get<double>(); + +json j2 = {1, 2, 3}; +auto v = j2.get<std::vector<int>>(); +auto s = j2.get<std::set<int>>(); +``` + +### Implicit Conversion + +When `JSON_ImplicitConversions` is enabled (default), implicit conversions +are available: + +```cpp +json j = 42; +int i = j; // implicit conversion +std::string s = j; // throws type_error::302 (wrong type) + +json j2 = "hello"; +std::string s2 = j2; // "hello" +``` + +To disable implicit conversions (recommended for new code): + +```cmake +set(JSON_ImplicitConversions OFF) +``` + +Or define the macro: + +```cpp +#define JSON_USE_IMPLICIT_CONVERSIONS 0 +``` + +When disabled, only explicit `get<T>()` works. diff --git a/docs/handbook/json4cpp/testing.md b/docs/handbook/json4cpp/testing.md new file mode 100644 index 0000000000..4439b71a42 --- /dev/null +++ b/docs/handbook/json4cpp/testing.md @@ -0,0 +1,190 @@ +# json4cpp — Testing + +## Test Framework + +The test suite uses **doctest** (a single-header C++ testing framework). +Tests are located in `json4cpp/tests/src/` with one file per feature area. + +## Test File Naming + +All test files follow the pattern `unit-<feature>.cpp`: + +| File | Covers | +|---|---| +| `unit-allocator.cpp` | Custom allocator support | +| `unit-alt-string.cpp` | Alternative string types | +| `unit-bson.cpp` | BSON serialization/deserialization | +| `unit-bjdata.cpp` | BJData format | +| `unit-capacity.cpp` | `size()`, `empty()`, `max_size()` | +| `unit-cbor.cpp` | CBOR format | +| `unit-class_const_iterator.cpp` | `const_iterator` behavior | +| `unit-class_iterator.cpp` | `iterator` behavior | +| `unit-class_lexer.cpp` | Lexer token scanning | +| `unit-class_parser.cpp` | Parser behavior | +| `unit-comparison.cpp` | Comparison operators | +| `unit-concepts.cpp` | Concept/type trait checks | +| `unit-constructor1.cpp` | Constructor overloads (part 1) | +| `unit-constructor2.cpp` | Constructor overloads (part 2) | +| `unit-convenience.cpp` | Convenience methods (`type_name`, etc.) | +| `unit-conversions.cpp` | Type conversions | +| `unit-deserialization.cpp` | Parsing from various sources | +| `unit-diagnostics.cpp` | `JSON_DIAGNOSTICS` mode | +| `unit-element_access1.cpp` | `operator[]`, `at()` (part 1) | +| `unit-element_access2.cpp` | `value()`, `front()`, `back()` (part 2) | +| `unit-hash.cpp` | `std::hash` specialization | +| `unit-inspection.cpp` | `is_*()`, `type()` methods | +| `unit-items.cpp` | `items()` iteration proxy | +| `unit-iterators1.cpp` | Forward iterators | +| `unit-iterators2.cpp` | Reverse iterators | +| `unit-json_patch.cpp` | JSON Patch (RFC 6902) | +| `unit-json_pointer.cpp` | JSON Pointer (RFC 6901) | +| `unit-large_json.cpp` | Large document handling | +| `unit-merge_patch.cpp` | Merge Patch (RFC 7396) | +| `unit-meta.cpp` | Library metadata | +| `unit-modifiers.cpp` | `push_back()`, `insert()`, `erase()`, etc. | +| `unit-msgpack.cpp` | MessagePack format | +| `unit-ordered_json.cpp` | `ordered_json` behavior | +| `unit-ordered_map.cpp` | `ordered_map` internals | +| `unit-pointer_access.cpp` | `get_ptr()` | +| `unit-readme.cpp` | Examples from README | +| `unit-reference_access.cpp` | `get_ref()` | +| `unit-regression1.cpp` | Regression tests (part 1) | +| `unit-regression2.cpp` | Regression tests (part 2) | +| `unit-serialization.cpp` | `dump()`, stream output | +| `unit-testsuites.cpp` | External test suites (JSONTestSuite, etc.) | +| `unit-to_chars.cpp` | Float-to-string conversion | +| `unit-ubjson.cpp` | UBJSON format | +| `unit-udt.cpp` | User-defined type conversions | +| `unit-udt_macro.cpp` | `NLOHMANN_DEFINE_TYPE_*` macros | +| `unit-unicode1.cpp` | Unicode handling (part 1) | +| `unit-unicode2.cpp` | Unicode handling (part 2) | +| `unit-unicode3.cpp` | Unicode handling (part 3) | +| `unit-unicode4.cpp` | Unicode handling (part 4) | +| `unit-unicode5.cpp` | Unicode handling (part 5) | +| `unit-wstring.cpp` | Wide string support | + +## CMake Configuration + +From `tests/CMakeLists.txt`: + +```cmake +# Test data files +file(GLOB_RECURSE JSON_TEST_DATA_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/data/*.json" + "${CMAKE_CURRENT_SOURCE_DIR}/data/*.cbor" + "${CMAKE_CURRENT_SOURCE_DIR}/data/*.msgpack" + "${CMAKE_CURRENT_SOURCE_DIR}/data/*.bson" + "${CMAKE_CURRENT_SOURCE_DIR}/data/*.ubjson" + "${CMAKE_CURRENT_SOURCE_DIR}/data/*.bjdata") + +# Each unit-*.cpp compiles to its own test executable +``` + +Key CMake options affecting tests: + +| Option | Effect | +|---|---| +| `JSON_BuildTests` | Enable/disable test building | +| `JSON_Diagnostics` | Build tests with `JSON_DIAGNOSTICS=1` | +| `JSON_Diagnostic_Positions` | Build tests with position tracking | +| `JSON_MultipleHeaders` | Use multi-header include paths | +| `JSON_ImplicitConversions` | Test with implicit conversions on/off | +| `JSON_DisableEnumSerialization` | Test with enum serialization off | +| `JSON_GlobalUDLs` | Test with global UDLs on/off | + +## Building Tests + +```bash +cd json4cpp +mkdir build && cd build + +# Configure with tests enabled +cmake .. -DJSON_BuildTests=ON + +# Build +cmake --build . + +# Run all tests +ctest --output-on-failure + +# Run a specific test +./tests/test-unit-json_pointer +``` + +## Test Structure + +Tests use doctest's `TEST_CASE` and `SECTION` macros: + +```cpp +#include <doctest/doctest.h> +#include <nlohmann/json.hpp> + +using json = nlohmann::json; + +TEST_CASE("element access") { + SECTION("array") { + json j = {1, 2, 3}; + + SECTION("operator[]") { + CHECK(j[0] == 1); + CHECK(j[1] == 2); + CHECK(j[2] == 3); + } + + SECTION("at()") { + CHECK(j.at(0) == 1); + CHECK_THROWS_AS(j.at(5), json::out_of_range); + } + } + + SECTION("object") { + json j = {{"key", "value"}}; + + CHECK(j["key"] == "value"); + CHECK(j.at("key") == "value"); + CHECK_THROWS_AS(j.at("missing"), json::out_of_range); + } +} +``` + +### Common Assertions + +| Macro | Purpose | +|---|---| +| `CHECK(expr)` | Value assertion (non-fatal) | +| `REQUIRE(expr)` | Value assertion (fatal) | +| `CHECK_THROWS_AS(expr, type)` | Exception type assertion | +| `CHECK_THROWS_WITH_AS(expr, msg, type)` | Exception message + type | +| `CHECK_NOTHROW(expr)` | No exception assertion | + +## Test Data + +The `tests/data/` directory contains JSON files for conformance testing: +- Input from JSONTestSuite (parsing edge cases) +- Binary format test vectors (CBOR, MessagePack, UBJSON, BSON, BJData) +- Unicode test cases +- Large nested structures + +## Running Specific Tests + +doctest supports command-line filtering: + +```bash +# Run tests matching a substring +./test-unit-json_pointer -tc="JSON pointer" + +# List all test cases +./test-unit-json_pointer -ltc + +# Run with verbose output +./test-unit-json_pointer -s +``` + +## Continuous Integration + +Tests are run across multiple compilers and platforms via CI (see `ci/` +directory). The `ci/supportedBranches.js` file lists which branches are +tested. The test matrix covers: +- GCC, Clang, MSVC +- C++11 through C++20 +- Various option combinations (diagnostics, implicit conversions, etc.) diff --git a/docs/handbook/json4cpp/value-types.md b/docs/handbook/json4cpp/value-types.md new file mode 100644 index 0000000000..c9f9bf0e6f --- /dev/null +++ b/docs/handbook/json4cpp/value-types.md @@ -0,0 +1,474 @@ +# json4cpp — Value Types + +## The `value_t` Enumeration + +Defined in `include/nlohmann/detail/value_t.hpp`, the `value_t` enumeration +identifies the type of a `basic_json` value: + +```cpp +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< unordered set of name/value pairs + array, ///< ordered collection of values + string, ///< string value + boolean, ///< boolean value + number_integer, ///< signed integer number + number_unsigned, ///< unsigned integer number + number_float, ///< floating-point number + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; +``` + +The underlying type is `std::uint8_t` (1 byte), stored in `m_data.m_type`. + +## Type-to-Storage Mapping + +| `value_t` | C++ Type Alias | Default C++ Type | Storage in `json_value` | +|---|---|---|---| +| `null` | — | — | No active member (pointer set to `nullptr`) | +| `object` | `object_t` | `std::map<std::string, basic_json>` | `object_t* object` | +| `array` | `array_t` | `std::vector<basic_json>` | `array_t* array` | +| `string` | `string_t` | `std::string` | `string_t* string` | +| `boolean` | `boolean_t` | `bool` | `boolean_t boolean` | +| `number_integer` | `number_integer_t` | `std::int64_t` | `number_integer_t number_integer` | +| `number_unsigned` | `number_unsigned_t` | `std::uint64_t` | `number_unsigned_t number_unsigned` | +| `number_float` | `number_float_t` | `double` | `number_float_t number_float` | +| `binary` | `binary_t` | `byte_container_with_subtype<vector<uint8_t>>` | `binary_t* binary` | +| `discarded` | — | — | No storage (used only during parse callback filtering) | + +Variable-length types (object, array, string, binary) are stored as **heap- +allocated pointers** to keep the `json_value` union at 8 bytes on 64-bit. + +## Type Inspection Methods + +### `type()` + +Returns the `value_t` of the stored value: + +```cpp +constexpr value_t type() const noexcept; +``` + +```cpp +json j = 42; +assert(j.type() == json::value_t::number_integer); + +json j2 = "hello"; +assert(j2.type() == json::value_t::string); +``` + +### `type_name()` + +Returns a human-readable string for the current type: + +```cpp +const char* type_name() const noexcept; +``` + +| `value_t` | Returned String | +|---|---| +| `null` | `"null"` | +| `object` | `"object"` | +| `array` | `"array"` | +| `string` | `"string"` | +| `boolean` | `"boolean"` | +| `binary` | `"binary"` | +| `number_integer` | `"number"` | +| `number_unsigned` | `"number"` | +| `number_float` | `"number"` | +| `discarded` | `"discarded"` | + +Note that all three numeric types return `"number"`. + +```cpp +json j = {1, 2, 3}; +std::cout << j.type_name(); // "array" +``` + +### `is_*()` Methods + +All return `constexpr bool` and are `noexcept`: + +```cpp +constexpr bool is_null() const noexcept; +constexpr bool is_boolean() const noexcept; +constexpr bool is_number() const noexcept; +constexpr bool is_number_integer() const noexcept; +constexpr bool is_number_unsigned() const noexcept; +constexpr bool is_number_float() const noexcept; +constexpr bool is_object() const noexcept; +constexpr bool is_array() const noexcept; +constexpr bool is_string() const noexcept; +constexpr bool is_binary() const noexcept; +constexpr bool is_discarded() const noexcept; +constexpr bool is_primitive() const noexcept; +constexpr bool is_structured() const noexcept; +``` + +### Category Methods + +```cpp +// is_primitive() == is_null() || is_string() || is_boolean() || is_number() || is_binary() +// is_structured() == is_array() || is_object() +// is_number() == is_number_integer() || is_number_float() +// is_number_integer() == (type == number_integer || type == number_unsigned) +``` + +Important: `is_number_integer()` returns `true` for **both** signed and unsigned +integers. Use `is_number_unsigned()` to distinguish. + +```cpp +json j = 42u; +j.is_number() // true +j.is_number_integer() // true +j.is_number_unsigned() // true + +json j2 = -5; +j2.is_number_integer() // true +j2.is_number_unsigned() // false +``` + +### `operator value_t()` + +Implicit conversion to `value_t`: + +```cpp +constexpr operator value_t() const noexcept; +``` + +```cpp +json j = "hello"; +json::value_t t = j; // value_t::string +``` + +## Null Type + +Null is the default value: + +```cpp +json j; // null +json j = nullptr; // null +json j(json::value_t::null); // null + +j.is_null() // true +j.type_name() // "null" +``` + +Null values have special behavior: +- `size()` returns 0 +- `empty()` returns `true` +- `operator[]` with a string key converts null to an object +- `operator[]` with a numeric index converts null to an array +- `push_back()` converts null to an array + +## Object Type + +### Internal Representation + +```cpp +using object_t = ObjectType<StringType, basic_json, + default_object_comparator_t, + AllocatorType<std::pair<const StringType, basic_json>>>; +``` + +Default: `std::map<std::string, basic_json, std::less<>, std::allocator<...>>` + +With C++14 transparent comparators, heterogeneous lookup is supported. + +### `ordered_json` Objects + +When using `ordered_json = basic_json<nlohmann::ordered_map>`, objects +preserve insertion order: + +```cpp +nlohmann::ordered_json j; +j["z"] = 1; +j["a"] = 2; +j["m"] = 3; +// iteration order: z, a, m +``` + +The `ordered_map` uses linear search (O(n) lookup) instead of tree-based +(O(log n)). + +## Array Type + +### Internal Representation + +```cpp +using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; +``` + +Default: `std::vector<basic_json, std::allocator<basic_json>>` + +Arrays can contain mixed types (heterogeneous): + +```cpp +json j = {1, "two", 3.0, true, nullptr, {{"nested", "object"}}}; +``` + +## String Type + +### Internal Representation + +```cpp +using string_t = StringType; // default: std::string +``` + +Strings in JSON are Unicode (UTF-8). The library validates UTF-8 during +serialization but stores raw bytes. The `dump()` method's `error_handler` +parameter controls what happens with invalid UTF-8: + +- `error_handler_t::strict` — throw `type_error::316` +- `error_handler_t::replace` — replace with U+FFFD +- `error_handler_t::ignore` — skip invalid bytes + +## Boolean Type + +```cpp +using boolean_t = BooleanType; // default: bool +``` + +Stored directly in the union (no heap allocation): + +```cpp +json j = true; +bool b = j.get<bool>(); +``` + +## Number Types + +### Three Distinct Types + +The library distinguishes three numeric types: + +```cpp +using number_integer_t = NumberIntegerType; // default: std::int64_t +using number_unsigned_t = NumberUnsignedType; // default: std::uint64_t +using number_float_t = NumberFloatType; // default: double +``` + +During parsing, the lexer determines the best-fit type: +1. If the number has a decimal point or exponent → `number_float` +2. If it fits in `int64_t` → `number_integer` +3. If it fits in `uint64_t` → `number_unsigned` +4. Otherwise → `number_float` (as approximation) + +### Cross-Type Comparisons + +Numbers of different types are compared correctly: + +```cpp +json(1) == json(1.0) // true +json(1) == json(1u) // true +json(-1) < json(0u) // true (signed < unsigned via cast) +``` + +The comparison logic in `JSON_IMPLEMENT_OPERATOR` handles all 6 cross-type +combinations (int×float, float×int, unsigned×float, float×unsigned, +unsigned×int, int×unsigned). + +### NaN Handling + +`NaN` values result in unordered comparisons: + +```cpp +json nan_val = std::numeric_limits<double>::quiet_NaN(); +nan_val == nan_val; // false (IEEE 754 semantics) +``` + +## Binary Type + +### Internal Representation + +```cpp +using binary_t = nlohmann::byte_container_with_subtype<BinaryType>; +// BinaryType default: std::vector<std::uint8_t> +``` + +`byte_container_with_subtype<BinaryType>` extends `BinaryType` with an +optional subtype tag: + +```cpp +template<typename BinaryType> +class byte_container_with_subtype : public BinaryType +{ +public: + using container_type = BinaryType; + using subtype_type = std::uint64_t; + + void set_subtype(subtype_type subtype_) noexcept; + constexpr subtype_type subtype() const noexcept; + constexpr bool has_subtype() const noexcept; + void clear_subtype() noexcept; + +private: + subtype_type m_subtype = 0; + bool m_has_subtype = false; +}; +``` + +### Creating Binary Values + +```cpp +// Without subtype +json j = json::binary({0x01, 0x02, 0x03, 0x04}); + +// With subtype +json j = json::binary({0x01, 0x02}, 128); + +// Access the binary container +json::binary_t& bin = j.get_binary(); +bin.push_back(0x05); +assert(bin.has_subtype() == false); // depends on how it was created +``` + +### Subtype Significance + +The subtype is meaningful for binary formats: +- **MessagePack**: maps to ext type +- **CBOR**: maps to tag +- **BSON**: maps to binary subtype + +JSON text format does not support binary data natively. + +## Discarded Type + +The `discarded` type is special — it's used only during parsing with +callbacks to indicate that a value should be excluded from the result: + +```cpp +json j = json::parse(input, [](int depth, json::parse_event_t event, json& parsed) { + if (event == json::parse_event_t::key && parsed == "secret") { + return false; // discard this key-value pair + } + return true; +}); +``` + +When `json::parse()` is called with `allow_exceptions=false` and parsing +fails, the result is a discarded value: + +```cpp +json j = json::parse("invalid", nullptr, false); +assert(j.is_discarded()); +``` + +## Type Ordering + +The `value_t` enumeration has a defined ordering used for cross-type +comparisons when values can't be compared directly: + +``` +null < boolean < number < object < array < string < binary +``` + +This means: + +```cpp +json(nullptr) < json(false); // true (null < boolean) +json(42) < json::object(); // true (number < object) +json("abc") > json::array(); // true (string > array) +``` + +The ordering is implemented via a lookup array in `value_t.hpp`: + +```cpp +static constexpr std::array<std::uint8_t, 9> order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ +}}; +``` + +All three numeric types share order index 2. + +## Type Conversions + +### Widening Conversions + +```cpp +json j = 42; // number_integer +double d = j.get<double>(); // OK: int → double + +json j2 = 3.14; // number_float +int i = j2.get<int>(); // OK: truncates to 3 +``` + +### Container Conversions + +```cpp +json arr = {1, 2, 3}; +auto v = arr.get<std::vector<int>>(); +auto l = arr.get<std::list<int>>(); +auto s = arr.get<std::set<int>>(); + +json obj = {{"a", 1}, {"b", 2}}; +auto m = obj.get<std::map<std::string, int>>(); +auto um = obj.get<std::unordered_map<std::string, int>>(); +``` + +### String Conversions + +```cpp +json j = 42; +std::string s = j.dump(); // "42" (serialization, not type conversion) +// j.get<std::string>() // throws type_error::302 + +json j = "hello"; +std::string s = j.get<std::string>(); // "hello" +``` + +## Constructing from `value_t` + +You can construct an empty value of a specific type: + +```cpp +json j_null(json::value_t::null); // null +json j_obj(json::value_t::object); // {} +json j_arr(json::value_t::array); // [] +json j_str(json::value_t::string); // "" +json j_bool(json::value_t::boolean); // false +json j_int(json::value_t::number_integer); // 0 +json j_uint(json::value_t::number_unsigned); // 0 +json j_float(json::value_t::number_float); // 0.0 +json j_bin(json::value_t::binary); // binary([], no subtype) +``` + +## Pointer Access + +Low-level pointer access to the underlying value: + +```cpp +json j = "hello"; + +// Returns nullptr if type doesn't match +const std::string* sp = j.get_ptr<const std::string*>(); +assert(sp != nullptr); + +const int* ip = j.get_ptr<const int*>(); +assert(ip == nullptr); // wrong type + +// Mutable pointer access +std::string* sp = j.get_ptr<std::string*>(); +*sp = "world"; +assert(j == "world"); +``` + +## Reference Access + +```cpp +json j = "hello"; + +const std::string& ref = j.get_ref<const std::string&>(); +assert(ref == "hello"); + +// Throws type_error::303 if type doesn't match +try { + const int& ref = j.get_ref<const int&>(); +} catch (json::type_error& e) { + // "incompatible ReferenceType for get_ref, actual type is string" +} +``` |
