Do not require string_t to be convertible from std::string

Three places built a std::string and handed it to something expecting a
string_t: the UBJSON high-precision number reader, which every binary reader
instantiates, and the BSON writer's array element size calculation and write.
That silently required string_t to be implicitly convertible from std::string,
which std::string itself and types with a string_view conversion satisfy, but
many string types do not.

Construct the string_t explicitly from the data and size, which the
requirements already cover. This makes boost::container::string, eastl::string,
std::pmr::string, and std::basic_string with a custom allocator work as
StringType, none of which could previously be used with any binary format.

Add binary format coverage to the alt_string test, which had none, including a
UBJSON high-precision number -- the case that goes through the reader path.
BSON stays uncovered there: it additionally needs string_t::find(value_type),
which alt_string does not provide.

Also record which containers from Boost, Abseil, and EASTL work for each
template parameter, and correct two claims: std::pmr::string is usable after
this change, and tsl::ordered_map is not usable at all, because its iterators
expose the mapped value as const while basic_json modifies it in place.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-08-28 17:38:56 +00:00
parent 5d93f35463
commit d386e0aa52
7 changed files with 87 additions and 14 deletions
+4 -1
View File
@@ -25,7 +25,8 @@ JSON class into byte-sized characters during deserialization.
Beyond the character type, the library expects a substantial part of the `#!cpp std::string` interface (contiguous
null-terminated `data()`, `substr()`, `find()`, `append()`, ...). See
[Template Parameter Requirements](../../features/types/template_parameters.md#stringtype) for the full list.
[Template Parameter Requirements](../../features/types/template_parameters.md#stringtype) for the full list and
for the string types that are known to work.
## Notes
@@ -82,3 +83,5 @@ and an example.
## Version history
- Added in version 1.0.0.
- Removed the requirement that `string_t` be implicitly convertible from `#!cpp std::string`, which the BSON writer and
the UBJSON reader relied on, in version 3.13.0.
+3 -1
View File
@@ -51,7 +51,9 @@ If you do want to preserve the **insertion order**, you can use the type [`nlohm
--8<-- "examples/ordered_json.output"
```
Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
Alternatively, you can use a more sophisticated map with a lookup index. `boost::unordered_flat_map`, `absl::flat_hash_map`, and `absl::node_hash_map` all work through a small adapter that restores the template argument order `basic_json` expects; see [Template Parameter Requirements](types/template_parameters.md#objecttype). Note these are *unordered*, not insertion-ordered.
[`tsl::ordered_map`](https://github.com/Tessil/ordered-map) cannot be used: its iterators expose the mapped value as `const`, while `basic_json` needs to modify it in place.
The [`ordered_map`](../api/ordered_map.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup
index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at
@@ -5,7 +5,7 @@ never formally states what a type passed for one of these parameters has to prov
the way the library uses the resulting [`object_t`](../../api/basic_json/object_t.md),
[`array_t`](../../api/basic_json/array_t.md), [`string_t`](../../api/basic_json/string_t.md), etc. This page collects
these requirements so they do not have to be discovered by trial and error. Each section also lists the concrete
types that are known to work for that parameter; the Abseil entries were checked against release 20250127.0.
types that are known to work for that parameter, checked against Boost 1.83, Abseil 20250127.0, and EASTL 3.21.
## How to read this page
@@ -46,7 +46,9 @@ Requirements are split into two groups:
incomplete type. `#!cpp std::map` and `#!cpp std::vector` are required by the standard to support incomplete
value types; most third-party containers are not, and inspecting the value type at class scope (for instance with
`#!cpp std::is_trivially_move_assignable`) makes them unusable as `ObjectType` or `ArrayType`. This rules out
`absl::btree_map` and `absl::InlinedVector`, among others, no matter how their template arguments are adapted.
`absl::btree_map`, `absl::InlinedVector`, `eastl::vector`, and `eastl::hash_map`, among others, no matter how their
template arguments are adapted. Boost.Container is the notable exception: it documents support for incomplete
types, and all of its containers work here.
## `ObjectType`
@@ -169,9 +171,12 @@ The library does not sort or de-duplicate keys itself; the behavior described in
| `#!cpp std::map` (default) | full |
| [`nlohmann::ordered_map`](../../api/ordered_map.md) | full; used by [`ordered_json`](../../api/ordered_json.md) |
| `#!cpp std::unordered_map`, through the adapter shown above | full |
| [`tsl::ordered_map`](https://github.com/Tessil/ordered-map), [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) | through the same adapter pattern; see [Object Order](../object_order.md) |
| `boost::container::map`, `boost::container::flat_map` | full, with no adapter |
| `boost::unordered_map`, `boost::unordered_flat_map`, `boost::unordered_node_map`, through the adapter shown above | full |
| `absl::flat_hash_map`, `absl::node_hash_map`, through the adapter shown above | full |
| `absl::btree_map` | not usable; requires a complete value type |
| `absl::btree_map`, `eastl::hash_map` | not usable; require a complete value type |
| `eastl::map` | not usable; EASTL iterators do not work with `#!cpp std::iterator_traits` |
| `tsl::ordered_map` | not usable; its iterators expose the mapped value as `#!cpp const` |
| `#!cpp std::multimap`, `#!cpp std::unordered_multimap` | not usable; `emplace` does not return `#!cpp std::pair<iterator, bool>` |
## `ArrayType`
@@ -214,7 +219,9 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
| `#!cpp std::vector` (default) | full |
| `#!cpp std::deque` | full; keeps references valid while the array grows, but see the note on `capacity()` above |
| `#!cpp std::list` | not usable; no `operator[]` and no random-access iterators |
| `absl::InlinedVector` | not usable; requires a complete value type |
| `boost::container::vector`, `boost::container::deque`, `boost::container::stable_vector` | full; `stable_vector` keeps references valid across every insertion |
| `boost::container::small_vector` | full, through an alias that fixes the inline capacity |
| `absl::InlinedVector`, `eastl::vector` | not usable; require a complete value type |
| `absl::FixedArray` | not usable; the size is fixed at construction |
## `StringType`
@@ -258,6 +265,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
| Functionality | Additional requirement |
|-------------------------------------------------------------|---------------------------------------------------|
| [`to_bson`](../../api/basic_json/to_bson.md) | `find(value_type)` and `npos` |
| [`parse`](../../api/basic_json/parse.md) from a `string_t` | the input adapters must accept it; otherwise pass a character range |
| [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) | a specialization of `#!cpp std::hash<StringType>` |
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
@@ -268,6 +276,23 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
### Compatible types
| Type | Support |
|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------|
| `#!cpp std::string` (default) | full |
| `#!cpp std::pmr::string`, `#!cpp std::basic_string` with a custom allocator | full |
| `boost::container::string` | full, once a `#!cpp std::hash` specialization is supplied (Boost provides `boost::hash` instead) |
| `eastl::string` | full, except that [`parse`](../../api/basic_json/parse.md) does not accept it directly; pass a character range or a `#!cpp std::string` |
| a custom string class in a user-defined namespace | full, if the requirements above are met |
| `#!cpp std::wstring`, `#!cpp std::u16string`, `#!cpp std::u32string` | not usable; the character type is not one byte wide |
| `absl::Cord` | not usable; no `value_type`, and the storage is not contiguous |
!!! tip "Reference implementation"
The unit test `tests/src/unit-alt-string.cpp` contains `alt_string`, a minimal string type that satisfies the
requirements needed for the tested subset of the API. It is a good starting point for a custom `StringType`.
### Compatible types
| Type | Support |
|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------|
| `#!cpp std::string` (default) | full |
@@ -470,7 +495,7 @@ such a container to a `basic_json` value.
| `#!cpp std::vector<std::uint8_t>` (default) | full |
| `#!cpp std::vector<char>` | full |
| `#!cpp std::vector<std::byte>` | full |
| `absl::InlinedVector<std::uint8_t, N>` | full |
| `absl::InlinedVector<std::uint8_t, N>`, `eastl::vector<std::uint8_t>`, `boost::container::vector<std::uint8_t>`, `boost::container::small_vector<std::uint8_t, N>` | full |
| `#!cpp std::string` | not usable; `binary_t::container_type` and `string_t` would be the same type, which makes the [`swap`](../../api/basic_json/swap.md) overloads ambiguous |
| containers whose `value_type` is wider than one byte | not usable |