Compare commits

..
Author SHA1 Message Date
Niels Lohmann 10cbcd9562 Merge branch 'develop' into issue-4378-enum-map-keys
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 22:22:29 +02:00
Niels Lohmann 93fd4aa6f6 Build the enum-keyed map test object instead of parsing it
ci_test_diagnostic_positions failed in unit-enum_keyed_maps_default.cpp:
with JSON_DIAGNOSTIC_POSITIONS, a parsed value adds its byte range to
the exception message ("(bytes 0-7) type must be array, but is
object"), so the exact-message checks did not match. Build the object
in memory, like unit-custom-array-type.cpp does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 20:37:04 +02:00
Niels Lohmann 103b37aa7d Move the default enum-keyed map tests out of unit-conversions.cpp
The Windows clang 20.1.8 job (MinGW, Debug) failed to link
test-conversions_cpp17 with "relocation truncated to fit:
IMAGE_REL_AMD64_REL32 against .rdata": the object file of
unit-conversions.cpp was already close to the limit, and the new
"maps with enum keys" test case pushed it over. windows.yml asks to keep
these objects small by splitting test files.

Move the test case unchanged into unit-enum_keyed_maps_default.cpp,
with the three enums it needs. It still honors a -D flag for
JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, as before. unit-conversions.cpp
is back to its state on develop.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-28 02:14:19 +02:00
Niels Lohmann 4671f04372 Merge branch 'develop' into issue-4378-enum-map-keys
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:57:48 +02:00
Niels Lohmann 99716a5ade Keep multimaps with enum keys as arrays of pairs
With JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, is_enum_keyed_map also matched
std::multimap and std::unordered_multimap. Storing them as objects throws
type_error.318 as soon as a key occurs twice, which is the normal case for
a multimap, so such values could no longer be serialized at all once the
macro was enabled, although they are stored losslessly as arrays of
[key, value] pairs without it.

Exclude maps with non-unique keys from is_enum_keyed_map. They are
detected by insert(value_type) returning an iterator rather than a
pair<iterator, bool>. Map-like types without such an insert() are still
treated as before.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 18:24:48 +02:00
Niels LohmannandMuhammad Amir bin Mohamad Ghazaly d914e3a027 Store maps with enum keys as objects (opt-in)
Maps with enum keys, such as std::map<E, T>, are stored as arrays of
[key, value] pairs, because enums are not convertible to the string type
of object keys - even if NLOHMANN_JSON_SERIALIZE_ENUM maps them to
strings (#4378).

The new JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS macro stores them as objects
instead, converting each key with the enum's to_json. It applies to any
map-like type with enum keys (std::map with any comparator,
std::unordered_map, ...). A key that does not convert to a string throws
type_error.302, and two keys converting to the same string throw the new
type_error.318, rather than losing an entry. The macro changes the output
of inline functions, so it is part of the ABI tag (_ekmo).

Reading needs no macro: std::map and std::unordered_map with enum keys
are now also read from objects, converting each key with the enum's
from_json. That input was rejected before, and arrays of pairs are still
read, so data written either way can be read.

This supersedes #4531, which first proposed storing these maps as
objects.

Co-authored-by: Muhammad Amir bin Mohamad Ghazaly <amirghaz@umich.edu>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 17:48:58 +02:00
27 changed files with 4667 additions and 553 deletions
+2
View File
@@ -53,6 +53,8 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
- [**JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS**](json_use_objects_for_enum_keyed_maps.md) - opt in to storing maps with enum
keys as objects
## Comparison behavior
@@ -0,0 +1,139 @@
# JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS /* value */
```
When defined to `1`, maps whose keys are enums (such as `std::map<E, T>` or `std::unordered_map<E, T>`) are stored as
JSON objects, using the enum's own conversion for the keys. By default, they are stored as arrays of `[key, value]`
pairs.
## Default definition
The default value is `0` (disabled — existing behavior is preserved).
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
```
## Notes
!!! note "Background"
JSON object keys are strings, so a map is only stored as an object if its keys can be converted to a string type.
Enums are not, even if [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps them to strings, so a
map with enum keys becomes an array of `[key, value]` pairs:
```json
[["stopped", "aa"], ["completed", "bb"]]
```
With this macro, the same map becomes an object
(see [#4378](https://github.com/nlohmann/json/issues/4378)):
```json
{"completed": "bb", "stopped": "aa"}
```
!!! note "Maps with non-unique keys"
Maps that allow duplicate keys, such as `std::multimap<E, T>` or `std::unordered_multimap<E, T>`, are not affected
by the macro and are still stored as arrays of `[key, value]` pairs, as an object cannot hold duplicate keys.
!!! note "Reading"
Reading is not affected by the macro: a map with enum keys can always be read from both an array of pairs and an
object. For the latter, each key is converted to the enum with its `from_json` function, e.g., the one defined by
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md). Data written without the macro can therefore
still be read after enabling it.
!!! warning "Keys must serialize to distinct strings"
Each key is converted with the enum's `to_json` function. If a key is not converted to a string (for instance, an
enum without [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md), which is stored as an integer, or an
enumerator mapped to `nullptr`), [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) is thrown.
If two keys are converted to the same string (for instance, because
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps an unlisted enumerator to the first entry),
[`type_error.318`](../../home/exceptions.md#jsonexceptiontype_error318) is thrown. In both cases, the target value
is not changed.
!!! warning "Opt-in only"
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
!!! note "ABI compatibility"
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_ekmo`), resulting in
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
## Examples
??? example "Default behavior (macro not defined)"
Without the macro, a map with enum keys is stored as an array of pairs:
```cpp
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
int main()
{
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// j is [["stopped","aa"],["completed","bb"]]
}
```
??? example "Objects for enum-keyed maps (macro defined to 1)"
With the macro, the same map is stored as an object:
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
int main()
{
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// j is {"completed":"bb","stopped":"aa"}
auto m2 = j.get<std::map<TaskState, std::string>>();
// m2 == m
}
```
## See also
- [Specializing enum conversion](../../features/enum_conversion.md)
- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum
- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with
exceptions
## Version history
- Added in version 3.13.0.
@@ -41,6 +41,9 @@ inline void from_json(const BasicJsonType& j, type& e);
conversion. Select this default pair carefully. See example 1 below.
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
list will be returned when converting to or from JSON. See example 2 below.
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
with the converted keys. Such maps can be read from both forms.
## Examples
@@ -80,6 +83,7 @@ inline void from_json(const BasicJsonType& j, type& e);
- [Specializing enum conversion](../../features/enum_conversion.md)
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
## Version history
@@ -44,6 +44,9 @@ inline void from_json(const BasicJsonType& j, type& e);
`"enum value out of range for <type>"`.
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
list will be returned when converting to or from JSON. See example 2 below.
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
with the converted keys. Such maps can be read from both forms.
## Examples
@@ -99,6 +102,7 @@ inline void from_json(const BasicJsonType& j, type& e);
- [Specializing enum conversion](../../features/enum_conversion.md)
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
## Version history
-11
View File
@@ -28,11 +28,6 @@ A minimal map-like container that preserves insertion order for use within [`nlo
The type uses a `std::vector` to store object elements. Therefore, adding elements can yield a reallocation in which
case all iterators (including the `end()` iterator) and all references to the elements are invalidated.
When the storage grows, the keys are copied and the mapped values are moved to the new storage. A plain `std::vector`
would copy the whole elements instead, because their `#!cpp const` keys make them not nothrow move constructible; for
[`ordered_json`](ordered_json.md), this would be a deep copy of every nested value. The values are only copied if
`T` is not default constructible or not nothrow move assignable.
## Member types
- **key_type** - key type (`Key`)
@@ -61,11 +56,6 @@ std::equal_to<> // since C++14
- **find**
- **insert**
## Exception safety
**emplace**, **operator\[\]**, and **insert(value)** have the strong exception guarantee: if an exception is thrown (for
instance, because copying a key or allocating memory fails), the contents of the container are unchanged.
## Complexity
Because the elements are stored in a `std::vector` in insertion order, there is no index to look a key up by. Every
@@ -132,4 +122,3 @@ This differs from `#!cpp std::map`, where the same operations are O(log n).
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](ordered_json.md).
- Added **key_compare** member in version 3.11.0.
- Changed in version 3.13.0: growing the storage moves the mapped values instead of copying them.
@@ -43,6 +43,23 @@ json jPi = 3.14;
assert(jPi.get<TaskState>() == TS_INVALID );
```
## Maps with enum keys
By default, maps with enum keys, such as `std::map<TaskState, std::string>`, are stored as arrays of `[key, value]`
pairs, because JSON object keys must be strings. Define
[`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) before including the
library to store them as objects, with the keys converted by the enum's `to_json()` function:
```cpp
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// default: [["stopped","aa"],["completed","bb"]]
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS: {"completed":"bb","stopped":"aa"}
```
Either form can be read back, with or without the macro.
## Notes
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
+7
View File
@@ -167,6 +167,13 @@ behavior is deprecated and switched off (`0`) by default.
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
## `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`
When defined to `1`, maps with enum keys (e.g., `std::map<E, T>`) are stored as objects, using the enum's conversion for
the keys, instead of arrays of `[key, value]` pairs. It is switched off (`0`) by default.
See [full documentation of `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md).
## `JSON_USE_SIMDUTF`
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
+2
View File
@@ -20,6 +20,8 @@ The complete default namespace name is derived as follows:
`_bics`.
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
appends `_ekmo`.
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
below.
+16
View File
@@ -596,6 +596,9 @@ During implicit or explicit value conversion, the JSON type must be compatible w
[json.exception.type_error.302] type must be string, but is object
```
This exception is also thrown with [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)
if a key of a map with enum keys is not converted to a string, for instance, because the enum is stored as an integer.
### json.exception.type_error.303
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
@@ -782,6 +785,19 @@ The dynamic type of the object cannot be represented in the requested serializat
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
### json.exception.type_error.318
With [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md), a map with enum
keys is stored as an object. This exception is thrown if two of its keys are converted to the same string, so one of the
entries would be lost. This happens, for instance, if [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md)
does not list an enumerator and it is therefore converted like the first listed one.
!!! failure "Example message"
```
[json.exception.type_error.318] duplicate object key 'red'
```
## Out of range
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
+1
View File
@@ -303,6 +303,7 @@ nav:
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
- 'JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS': api/macros/json_use_objects_for_enum_keyed_maps.md
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
+15 -4
View File
@@ -46,6 +46,10 @@
#define JSON_STRICT_NUL_HANDLING 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -82,14 +86,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -98,7 +108,8 @@
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -550,11 +550,40 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t)
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
}
// read a map with enum keys from an object, using the enum's own from_json for
// the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/)
{
if (!j.is_object())
{
return false;
}
m.clear();
for (const auto& p : *j.template get_ptr<const typename BasicJsonType::object_t*>())
{
m.emplace(BasicJsonType(p.first).template get<typename Map::key_type>(), p.second.template get<typename Map::mapped_type>());
}
return true;
}
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/)
{
return false;
}
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
typename = enable_if_t < !std::is_constructible <
typename BasicJsonType::string_t, Key >::value >>
inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
@@ -575,6 +604,11 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
typename BasicJsonType::string_t, Key >::value >>
inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
@@ -23,6 +23,7 @@
#include <valarray> // valarray
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/std_fs.hpp>
@@ -381,6 +382,9 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleArrayType>::value
#endif
,
int > = 0 >
@@ -435,6 +439,33 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
external_constructor<value_t::object>::construct(j, obj);
}
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
// store a map with enum keys as an object, using the enum's own to_json for the
// keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); without the macro, such maps
// are stored as arrays of [key, value] pairs
template < typename BasicJsonType, typename EnumKeyedMap,
enable_if_t < is_enum_keyed_map<EnumKeyedMap>::value&& !is_basic_json<EnumKeyedMap>::value, int > = 0 >
inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
{
typename BasicJsonType::object_t obj;
for (const auto& p : map)
{
BasicJsonType key = p.first;
if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
{
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key));
}
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
{
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key));
}
}
external_constructor<value_t::object>::construct(j, std::move(obj));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
@@ -46,6 +46,7 @@
#undef JSON_BRACE_INIT_COPY_SEMANTICS
#undef JSON_PRECISE_STREAM_POSITION
#undef JSON_STRICT_NUL_HANDLING
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
@@ -400,6 +400,30 @@ template<typename BasicJsonType, typename CompatibleObjectType>
struct is_compatible_object_type
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
template<typename T>
using insert_result_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()));
template<typename T>
using insert_result_second_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()).second);
// a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename T, typename = void>
struct is_enum_keyed_map : std::false_type {};
template<typename T>
struct is_enum_keyed_map <
T, enable_if_t < is_detected<mapped_type_t, T>::value&&
is_detected<key_type_t, T>::value >>
{
// maps with non-unique keys (std::multimap, std::unordered_multimap, ...)
// are excluded, because an object cannot hold duplicate keys; they are
// detected by insert() returning an iterator instead of a pair<iterator, bool>
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
static constexpr bool value = std::is_enum<typename T::key_type>::value &&
!(is_detected<insert_result_t, T>::value && !is_detected<insert_result_second_t, T>::value);
};
template<typename BasicJsonType, typename ConstructibleObjectType,
typename = void>
struct is_constructible_object_type_impl : std::false_type {};
+5 -65
View File
@@ -8,15 +8,13 @@
#pragma once
#include <algorithm> // max, min
#include <functional> // equal_to, less
#include <initializer_list> // initializer_list
#include <iterator> // input_iterator_tag, iterator_traits
#include <memory> // allocator
#include <stdexcept> // for out_of_range
#include <tuple> // forward_as_tuple
#include <type_traits> // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible
#include <utility> // forward, move, pair, piecewise_construct
#include <type_traits> // enable_if, is_convertible
#include <utility> // pair
#include <vector> // vector
#include <nlohmann/detail/macro_scope.hpp>
@@ -81,7 +79,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {it, false};
}
}
append(key, std::forward<T>(t));
Container::emplace_back(key, std::forward<T>(t));
return {std::prev(this->end()), true};
}
@@ -96,7 +94,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {it, false};
}
}
append(std::forward<KeyType>(key), std::forward<T>(t));
Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));
return {std::prev(this->end()), true};
}
@@ -370,7 +368,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {it, false};
}
}
append(value);
Container::push_back(value);
return {--this->end(), true};
}
@@ -388,64 +386,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
}
private:
/*!
@brief add an element whose key is not yet contained at the end
A std::vector copies all elements when it grows, because their const keys
make them not nothrow move constructible. For ordered_json, this is a deep
copy of every value. Where the strong exception guarantee can be kept, grow
the storage here instead, copying only the keys and moving the values.
*/
template<typename... Args>
void append(Args&& ... args)
{
// evaluated here rather than at class scope, because T is still
// incomplete when basic_json instantiates its object_t
using move_values = std::integral_constant<bool, detail::conjunction<
detail::negation<std::is_nothrow_move_constructible<value_type>>,
std::is_copy_constructible<key_type>,
detail::is_default_constructible<mapped_type>,
std::is_nothrow_move_assignable<mapped_type>>::value>;
append_impl(move_values{}, std::forward<Args>(args)...);
}
template<typename... Args>
void append_impl(std::true_type /*unused*/, Args&& ... args)
{
if (this->size() < this->capacity())
{
Container::emplace_back(std::forward<Args>(args)...);
return;
}
// 1. May throw, but only changes tmp: copy the keys, value-initialize
// the values, and add the new element. The arguments may refer to
// elements of this container, so they are used before any value is
// moved out of it.
Container tmp(this->get_allocator()); // equal allocators, so swap() is valid
tmp.reserve((std::min)(this->max_size(), (std::max)(size_type{1}, 2 * this->size())));
for (const auto& element : *this)
{
tmp.emplace_back(std::piecewise_construct, std::forward_as_tuple(element.first), std::forward_as_tuple());
}
tmp.emplace_back(std::forward<Args>(args)...);
// 2. Cannot throw: move the values over and adopt the new storage.
auto it = tmp.begin();
for (auto& element : *this)
{
it->second = std::move(element.second);
++it;
}
Container::swap(tmp);
}
template<typename... Args>
void append_impl(std::false_type /*unused*/, Args&& ... args)
{
Container::emplace_back(std::forward<Args>(args)...);
}
JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();
};
+3840
View File
File diff suppressed because it is too large Load Diff
+111 -69
View File
@@ -103,6 +103,10 @@
#define JSON_STRICT_NUL_HANDLING 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -139,14 +143,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -155,7 +165,8 @@
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -4410,6 +4421,30 @@ template<typename BasicJsonType, typename CompatibleObjectType>
struct is_compatible_object_type
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
template<typename T>
using insert_result_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()));
template<typename T>
using insert_result_second_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()).second);
// a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename T, typename = void>
struct is_enum_keyed_map : std::false_type {};
template<typename T>
struct is_enum_keyed_map <
T, enable_if_t < is_detected<mapped_type_t, T>::value&&
is_detected<key_type_t, T>::value >>
{
// maps with non-unique keys (std::multimap, std::unordered_multimap, ...)
// are excluded, because an object cannot hold duplicate keys; they are
// detected by insert() returning an iterator instead of a pair<iterator, bool>
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
static constexpr bool value = std::is_enum<typename T::key_type>::value &&
!(is_detected<insert_result_t, T>::value && !is_detected<insert_result_second_t, T>::value);
};
template<typename BasicJsonType, typename ConstructibleObjectType,
typename = void>
struct is_constructible_object_type_impl : std::false_type {};
@@ -6047,11 +6082,40 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t)
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
}
// read a map with enum keys from an object, using the enum's own from_json for
// the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/)
{
if (!j.is_object())
{
return false;
}
m.clear();
for (const auto& p : *j.template get_ptr<const typename BasicJsonType::object_t*>())
{
m.emplace(BasicJsonType(p.first).template get<typename Map::key_type>(), p.second.template get<typename Map::mapped_type>());
}
return true;
}
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/)
{
return false;
}
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
typename = enable_if_t < !std::is_constructible <
typename BasicJsonType::string_t, Key >::value >>
inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
@@ -6072,6 +6136,11 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
typename BasicJsonType::string_t, Key >::value >>
inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
@@ -6167,6 +6236,8 @@ NLOHMANN_JSON_NAMESPACE_END
#include <valarray> // valarray
#include <vector> // vector
// #include <nlohmann/detail/exceptions.hpp>
// #include <nlohmann/detail/iterators/iteration_proxy.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
@@ -6910,6 +6981,9 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleArrayType>::value
#endif
,
int > = 0 >
@@ -6964,6 +7038,33 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
external_constructor<value_t::object>::construct(j, obj);
}
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
// store a map with enum keys as an object, using the enum's own to_json for the
// keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); without the macro, such maps
// are stored as arrays of [key, value] pairs
template < typename BasicJsonType, typename EnumKeyedMap,
enable_if_t < is_enum_keyed_map<EnumKeyedMap>::value&& !is_basic_json<EnumKeyedMap>::value, int > = 0 >
inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
{
typename BasicJsonType::object_t obj;
for (const auto& p : map)
{
BasicJsonType key = p.first;
if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
{
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key));
}
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
{
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key));
}
}
external_constructor<value_t::object>::construct(j, std::move(obj));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
@@ -25768,15 +25869,13 @@ NLOHMANN_JSON_NAMESPACE_END
#include <algorithm> // max, min
#include <functional> // equal_to, less
#include <initializer_list> // initializer_list
#include <iterator> // input_iterator_tag, iterator_traits
#include <memory> // allocator
#include <stdexcept> // for out_of_range
#include <tuple> // forward_as_tuple
#include <type_traits> // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible
#include <utility> // forward, move, pair, piecewise_construct
#include <type_traits> // enable_if, is_convertible
#include <utility> // pair
#include <vector> // vector
// #include <nlohmann/detail/macro_scope.hpp>
@@ -25843,7 +25942,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {it, false};
}
}
append(key, std::forward<T>(t));
Container::emplace_back(key, std::forward<T>(t));
return {std::prev(this->end()), true};
}
@@ -25858,7 +25957,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {it, false};
}
}
append(std::forward<KeyType>(key), std::forward<T>(t));
Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));
return {std::prev(this->end()), true};
}
@@ -26132,7 +26231,7 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {it, false};
}
}
append(value);
Container::push_back(value);
return {--this->end(), true};
}
@@ -26150,64 +26249,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
}
private:
/*!
@brief add an element whose key is not yet contained at the end
A std::vector copies all elements when it grows, because their const keys
make them not nothrow move constructible. For ordered_json, this is a deep
copy of every value. Where the strong exception guarantee can be kept, grow
the storage here instead, copying only the keys and moving the values.
*/
template<typename... Args>
void append(Args&& ... args)
{
// evaluated here rather than at class scope, because T is still
// incomplete when basic_json instantiates its object_t
using move_values = std::integral_constant<bool, detail::conjunction<
detail::negation<std::is_nothrow_move_constructible<value_type>>,
std::is_copy_constructible<key_type>,
detail::is_default_constructible<mapped_type>,
std::is_nothrow_move_assignable<mapped_type>>::value>;
append_impl(move_values{}, std::forward<Args>(args)...);
}
template<typename... Args>
void append_impl(std::true_type /*unused*/, Args&& ... args)
{
if (this->size() < this->capacity())
{
Container::emplace_back(std::forward<Args>(args)...);
return;
}
// 1. May throw, but only changes tmp: copy the keys, value-initialize
// the values, and add the new element. The arguments may refer to
// elements of this container, so they are used before any value is
// moved out of it.
Container tmp(this->get_allocator()); // equal allocators, so swap() is valid
tmp.reserve((std::min)(this->max_size(), (std::max)(size_type{1}, 2 * this->size())));
for (const auto& element : *this)
{
tmp.emplace_back(std::piecewise_construct, std::forward_as_tuple(element.first), std::forward_as_tuple());
}
tmp.emplace_back(std::forward<Args>(args)...);
// 2. Cannot throw: move the values over and adopt the new storage.
auto it = tmp.begin();
for (auto& element : *this)
{
it->second = std::move(element.second);
++it;
}
Container::swap(tmp);
}
template<typename... Args>
void append_impl(std::false_type /*unused*/, Args&& ... args)
{
Container::emplace_back(std::forward<Args>(args)...);
}
JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();
};
@@ -32807,6 +32848,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#undef JSON_BRACE_INIT_COPY_SEMANTICS
#undef JSON_PRECISE_STREAM_POSITION
#undef JSON_STRICT_NUL_HANDLING
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
+15 -4
View File
@@ -64,6 +64,10 @@
#define JSON_STRICT_NUL_HANDLING 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -100,14 +104,20 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -116,7 +126,8 @@
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+4
View File
@@ -44,6 +44,10 @@ TEST_CASE("default namespace")
expected += "_snul";
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
expected += "_ekmo";
#endif
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
+4
View File
@@ -45,6 +45,10 @@ TEST_CASE("default namespace without version component")
expected += "_snul";
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
expected += "_ekmo";
#endif
expected += "::basic_json";
// fallback for Clang
-33
View File
@@ -119,39 +119,6 @@ BENCHMARK_CAPTURE(ParseIndented, canada / 4, TEST_DATA_DIRECTORY "/nativej
BENCHMARK_CAPTURE(ParseIndented, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4);
BENCHMARK_CAPTURE(ParseIndented, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4);
//////////////////////////////////////////////////////////////////////////////
// parse JSON from string into an ordered_json
//
// Same as ParseString above, but with nlohmann::ordered_json, whose objects
// keep their members in a vector: the pair of rows shows what preserving the
// insertion order costs.
//////////////////////////////////////////////////////////////////////////////
static void ParseStringOrdered(benchmark::State& state, const char* filename)
{
std::ifstream f(filename);
std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
while (state.KeepRunning())
{
state.PauseTiming();
auto* j = new nlohmann::ordered_json();
state.ResumeTiming();
*j = nlohmann::ordered_json::parse(str);
state.PauseTiming();
delete j;
state.ResumeTiming();
}
state.SetBytesProcessed(state.iterations() * str.size());
}
BENCHMARK_CAPTURE(ParseStringOrdered, jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json");
BENCHMARK_CAPTURE(ParseStringOrdered, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(ParseStringOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(ParseStringOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// serialize JSON
//////////////////////////////////////////////////////////////////////////////
-18
View File
@@ -46,24 +46,6 @@ TEST_CASE("Tests with disabled exceptions")
CHECK(*sax_no_exception::error_string == "[json.exception.parse_error.101] parse error at line 1, column 1: syntax error while parsing value - invalid literal; last read: 'x'");
delete sax_no_exception::error_string; // NOLINT(cppcoreguidelines-owning-memory)
}
SECTION("growing an ordered_json object")
{
auto j = nlohmann::ordered_json::object();
for (int i = 0; i < 100; ++i)
{
j[std::to_string(i)] = {{"nested", i}};
}
CHECK(j.size() == 100);
int i = 0;
for (const auto& element : j.items())
{
CHECK(element.key() == std::to_string(i));
CHECK(element.value()["nested"] == i);
++i;
}
}
}
DOCTEST_GCC_SUPPRESS_WARNING_POP
+246
View File
@@ -0,0 +1,246 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// skip tests if JSON_DisableEnumSerialization=ON (#4384)
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
#endif
// This file tests the opt-in JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, so it defines
// the macro itself rather than relying on a -D flag, and runs in every build.
// The default behavior is tested in unit-enum_keyed_maps_default.cpp.
#ifdef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
#include <nlohmann/json.hpp>
using nlohmann::json;
using nlohmann::ordered_json;
#include <cstddef>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#define STRINGIZE_EX(x) #x
#define STRINGIZE(x) STRINGIZE_EX(x)
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
namespace
{
// std::hash is only required for enums since C++14
struct enum_hash
{
template<typename T>
std::size_t operator()(T t) const noexcept
{
return static_cast<std::size_t>(t);
}
};
} // namespace
// the example from #4378
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
{
TS_STOPPED,
TS_RUNNING,
TS_COMPLETED,
TS_INVALID = -1,
};
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
{
{TS_INVALID, nullptr},
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
enum class color {red, green, blue}; // blue is not mapped and falls back to "red"
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(color,
{
{color::red, "red"},
{color::green, "green"},
})
enum class strict_color {red, green, blue}; // blue is not mapped
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color,
{
{strict_color::red, "red"},
{strict_color::green, "green"},
})
enum class digit {zero, one};
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(digit,
{
{digit::zero, 0},
{digit::one, 1},
})
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
enum class plain {zero, one}; // serialized as integer
#endif
TEST_CASE("JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS")
{
SECTION("the macro is part of the ABI tag")
{
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
CHECK(ns.find("_ekmo") != std::string::npos);
}
SECTION("std::map (#4378)")
{
using task_map = std::map<TaskState, std::string>;
const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
const json j = m;
CHECK(j == json::parse(R"({"stopped":"aa","completed":"bb"})"));
CHECK(j.get<task_map>() == m);
json j2;
j2["x"] = m;
CHECK(j2.dump() == R"({"x":{"completed":"bb","stopped":"aa"}})");
}
SECTION("std::map with custom comparator")
{
using task_map = std::map<TaskState, int, std::greater<TaskState>>;
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}};
const json j = m;
CHECK(j == json::parse(R"({"stopped":1,"running":2})"));
CHECK(j.get<task_map>() == m);
}
SECTION("std::unordered_map")
{
using task_map = std::unordered_map<TaskState, int, enum_hash>;
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}};
const json j = m;
CHECK(j == json::parse(R"({"stopped":1,"running":2})"));
CHECK(j.get<task_map>() == m);
}
SECTION("nested maps")
{
using nested_map = std::map<color, std::map<TaskState, int>>;
const nested_map m = {{color::green, {{TS_RUNNING, 1}}}, {color::red, {}}};
const json j = m;
CHECK(j == json::parse(R"({"green":{"running":1},"red":{}})"));
CHECK(j.get<nested_map>() == m);
}
SECTION("ordered_json keeps the order of the map")
{
using task_map = std::map<TaskState, int>;
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}, {TS_COMPLETED, 3}};
const ordered_json j = m;
CHECK(j.dump() == R"({"stopped":1,"running":2,"completed":3})");
CHECK(j.get<task_map>() == m);
}
SECTION("empty map")
{
const json j = std::map<TaskState, int>();
CHECK(j.is_object());
CHECK(j.empty());
}
SECTION("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT")
{
using color_map = std::map<strict_color, int>;
const color_map m = {{strict_color::red, 1}, {strict_color::green, 2}};
const json j = m;
CHECK(j == json::parse(R"({"red":1,"green":2})"));
CHECK(j.get<color_map>() == m);
const color_map unmapped = {{strict_color::blue, 1}};
json _;
CHECK_THROWS_WITH_AS(_ = unmapped,
"[json.exception.out_of_range.410] enum value out of range for strict_color", json::out_of_range&);
}
SECTION("arrays of [key, value] pairs are still read")
{
using task_map = std::map<TaskState, int>;
const task_map m = {{TS_STOPPED, 1}};
CHECK(json::parse(R"([["stopped",1]])").get<task_map>() == m);
}
SECTION("other containers are not affected")
{
const std::vector<std::pair<TaskState, int>> pairs = {{TS_STOPPED, 1}};
const std::map<std::string, TaskState> string_keys = {{"a", TS_STOPPED}};
const std::map<int, int> int_keys = {{1, 2}};
CHECK(json(pairs) == json::parse(R"([["stopped",1]])"));
CHECK(json(string_keys) == json::parse(R"({"a":"stopped"})"));
CHECK(json(int_keys) == json::parse("[[1,2]]"));
}
SECTION("maps with non-unique keys are still stored as arrays of pairs")
{
const std::multimap<TaskState, int> mm = {{TS_STOPPED, 1}, {TS_STOPPED, 2}};
const std::unordered_multimap<TaskState, int, enum_hash> umm = {{TS_RUNNING, 3}, {TS_RUNNING, 3}};
CHECK(json(mm) == json::parse(R"([["stopped",1],["stopped",2]])"));
CHECK(json(umm) == json::parse(R"([["running",3],["running",3]])"));
}
SECTION("keys that do not serialize to strings")
{
const std::map<TaskState, int> null_key = {{TS_INVALID, 1}};
const std::map<digit, int> number_key = {{digit::zero, 1}};
json j = "unchanged";
// mapped to null
CHECK_THROWS_WITH_AS(j = null_key,
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
// mapped to a number
CHECK_THROWS_WITH_AS(j = number_key,
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
// enum without NLOHMANN_JSON_SERIALIZE_ENUM
const std::map<plain, int> plain_key = {{plain::zero, 1}};
CHECK_THROWS_WITH_AS(j = plain_key,
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
#endif
CHECK(j == "unchanged");
}
SECTION("keys that serialize to the same string")
{
const std::map<color, int> m = {{color::red, 1}, {color::blue, 2}};
json j = "unchanged";
// color::blue is not mapped and falls back to "red"
CHECK_THROWS_WITH_AS(j = m,
"[json.exception.type_error.318] duplicate object key 'red'", json::type_error&);
CHECK(j == "unchanged");
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+144
View File
@@ -0,0 +1,144 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// This file tests maps with enum keys with the default setting of
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS (or whatever a -D flag sets it to).
// unit-enum_keyed_maps.cpp tests JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1.
// These tests are not part of unit-conversions.cpp, because that object file
// is already too big for the MinGW linker of some compilers.
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cstddef>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
enum class cards {kreuz, pik, herz, karo};
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
{
{cards::kreuz, "kreuz"},
{cards::pik, "pik"},
{cards::herz, "herz"},
{cards::karo, "karo"}
})
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
{
TS_STOPPED,
TS_RUNNING,
TS_COMPLETED,
TS_INVALID = -1,
};
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
{
{TS_INVALID, nullptr},
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
{
{strict_cards::kreuz, "kreuz"},
{strict_cards::pik, "pik"},
{strict_cards::herz, "herz"},
{strict_cards::karo, "karo"}
})
namespace
{
// std::hash is only required for enums since C++14
struct enum_hash
{
template<typename T>
std::size_t operator()(T t) const noexcept
{
return static_cast<std::size_t>(t);
}
};
} // namespace
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
TEST_CASE("maps with enum keys")
{
using task_map = std::map<TaskState, std::string>;
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
using nested_map = std::map<cards, std::map<TaskState, int>>;
using strict_map = std::map<strict_cards, int>;
using int_map = std::map<int, int>;
using int_umap = std::unordered_map<int, int>;
const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
#if !JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
SECTION("stored as array of pairs")
{
CHECK(json(m) == json::parse(R"([["stopped","aa"],["completed","bb"]])"));
CHECK(json(task_umap {{TS_RUNNING, "cc"}}) == json::parse(R"([["running","cc"]])"));
}
#endif
SECTION("read from array of pairs")
{
CHECK(json::parse(R"([["stopped","aa"],["completed","bb"]])").get<task_map>() == m);
}
SECTION("read from object (#4378)")
{
const json j = json::parse(R"({"stopped":"aa","completed":"bb"})");
CHECK(j.get<task_map>() == m);
CHECK(j.get<task_umap>() == task_umap(m.begin(), m.end()));
CHECK(j.get<task_gmap>() == task_gmap(m.begin(), m.end()));
CHECK(json::parse(R"({"kreuz":{"stopped":1}})").get<nested_map>() == nested_map {{cards::kreuz, {{TS_STOPPED, 1}}}});
CHECK(nlohmann::ordered_json::parse(R"({"stopped":"aa","completed":"bb"})").get<task_map>() == m);
// object keys go through the enum's from_json
strict_map sm;
CHECK_THROWS_WITH_AS(json::parse(R"({"what?":1})").get_to(sm),
"[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&);
}
SECTION("objects are only read for enum keys")
{
// built rather than parsed, so that the messages do not gain a byte
// range with JSON_DIAGNOSTIC_POSITIONS
const json j = {{"1", 2}};
int_map im;
int_umap ium;
CHECK_THROWS_WITH_AS(j.get_to(im),
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
CHECK_THROWS_WITH_AS(j.get_to(ium),
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
}
SECTION("other types are rejected")
{
task_map tm;
CHECK_THROWS_WITH_AS(json("stopped").get_to(tm),
"[json.exception.type_error.302] type must be array, but is string", json::type_error&);
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
-348
View File
@@ -11,87 +11,6 @@
#include <nlohmann/json.hpp>
using nlohmann::ordered_map;
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace
{
// number of copies made of counted values
int value_copies = 0;
// a mapped type that counts its copies; moving from it leaves -1 behind
struct counted // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
{
int payload = 0;
counted() = default;
explicit counted(int p) noexcept : payload(p) {}
counted(const counted& other) : payload(other.payload)
{
++value_copies;
}
counted(counted&& other) noexcept : payload(other.payload)
{
other.payload = -1;
}
counted& operator=(const counted&) = delete;
counted& operator=(counted&& other) noexcept
{
payload = other.payload;
other.payload = -1;
return *this;
}
};
#if !defined(JSON_NOEXCEPTION)
// number of throwing_key copies that still succeed; the next one throws
// (a negative value means that copies never throw)
int key_copies_until_throw = -1;
// a key type whose copy constructor can be made to throw
struct throwing_key // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
{
int id = 0;
explicit throwing_key(int i) noexcept : id(i) {}
throwing_key(const throwing_key& other) : id(other.id)
{
if (key_copies_until_throw == 0)
{
throw std::runtime_error("key copy failed");
}
if (key_copies_until_throw > 0)
{
--key_copies_until_throw;
}
}
throwing_key& operator=(const throwing_key&) = delete;
friend bool operator==(const throwing_key& lhs, const throwing_key& rhs) noexcept
{
return lhs.id == rhs.id;
}
};
#endif
// a mapped type that cannot be default-constructed
struct no_default
{
explicit no_default(int v) noexcept : value(v) {}
int value;
};
// ordered_json must keep moving its values when an object grows
using ordered_object_t = nlohmann::ordered_json::object_t;
static_assert(!std::is_nothrow_move_constructible<ordered_object_t::value_type>::value, "std::vector would move the elements itself");
static_assert(std::is_copy_constructible<ordered_object_t::key_type>::value, "keys must be copyable");
static_assert(std::is_default_constructible<ordered_object_t::mapped_type>::value, "values must be default-constructible");
static_assert(std::is_nothrow_move_assignable<ordered_object_t::mapped_type>::value, "values must be nothrow move-assignable");
} // namespace
TEST_CASE("ordered_map")
{
SECTION("constructor")
@@ -394,270 +313,3 @@ TEST_CASE("ordered_map")
}
}
}
TEST_CASE("ordered_map growth")
{
SECTION("values are moved, not copied, when the storage grows")
{
ordered_map<std::string, counted> om;
std::size_t growths = 0;
value_copies = 0;
// inserts 100 elements with the given function and counts the growths
const auto fill = [&om, &growths](void (*insert)(ordered_map<std::string, counted>&, int))
{
for (int i = 0; i < 100; ++i)
{
const auto old_capacity = om.capacity();
insert(om, i);
if (om.capacity() > old_capacity)
{
++growths;
}
}
};
// checks that the elements are in insertion order with their values
const auto check_contents = [&om]
{
CHECK(om.size() == 100);
int i = 0;
for (const auto& element : om)
{
CHECK(element.first == std::to_string(i));
CHECK(element.second.payload == i);
++i;
}
};
SECTION("emplace")
{
fill([](ordered_map<std::string, counted>& m, int i)
{
m.emplace(std::to_string(i), counted(i));
});
CHECK(growths >= 3);
CHECK(value_copies == 0);
check_contents();
}
SECTION("operator[]")
{
fill([](ordered_map<std::string, counted>& m, int i)
{
m[std::to_string(i)] = counted(i);
});
CHECK(growths >= 3);
CHECK(value_copies == 0);
check_contents();
}
SECTION("insert(value_type&&)")
{
fill([](ordered_map<std::string, counted>& m, int i)
{
m.insert({std::to_string(i), counted(i)});
});
CHECK(growths >= 3);
CHECK(value_copies == 0);
check_contents();
}
SECTION("insert(const value_type&)")
{
fill([](ordered_map<std::string, counted>& m, int i)
{
const std::pair<const std::string, counted> value(std::to_string(i), counted(i));
m.insert(value);
});
CHECK(growths >= 3);
// only the inserted values are copied
CHECK(value_copies == 100);
check_contents();
}
SECTION("insert(first, last)")
{
std::vector<std::pair<const std::string, counted>> values;
values.reserve(100);
for (int i = 0; i < 100; ++i)
{
values.emplace_back(std::to_string(i), counted(i));
}
value_copies = 0;
om.insert(values.cbegin(), values.cend());
// only the inserted values are copied
CHECK(value_copies == 100);
check_contents();
}
}
SECTION("elements keep their order and values over many growths")
{
ordered_map<std::string, counted> om;
for (int i = 0; i < 1000; ++i)
{
om.emplace(std::to_string(i), counted(i));
}
CHECK(om.size() == 1000);
int i = 0;
for (const auto& element : om)
{
CHECK(element.first == std::to_string(i));
CHECK(element.second.payload == i);
++i;
}
}
SECTION("arguments may refer to elements of the full container")
{
SECTION("moving a value out of the container")
{
ordered_map<std::string, counted> om;
om.reserve(4);
while (om.size() < om.capacity())
{
const auto i = static_cast<int>(om.size());
om.emplace(std::to_string(i), counted(i));
}
const auto size = om.size();
om.emplace("new", std::move(om.at("0")));
CHECK(om.size() == size + 1);
CHECK(om.at("new").payload == 0);
CHECK(om.at("0").payload == -1);
}
SECTION("using a value as key")
{
ordered_map<std::string, std::string> om;
om.reserve(4);
while (om.size() < om.capacity())
{
const auto i = std::to_string(om.size());
om.emplace("k" + i, "v" + i);
}
const auto size = om.size();
om.emplace(om.at("k0"), std::string("x"));
CHECK(om.size() == size + 1);
CHECK(om.at("k0") == "v0");
CHECK(om.at("v0") == "x");
}
SECTION("ordered_json")
{
auto j = nlohmann::ordered_json::object();
auto& object = j.get_ref<nlohmann::ordered_json::object_t&>();
object.reserve(4);
while (object.size() < object.capacity())
{
const auto i = std::to_string(object.size());
j[i] = "a value that is too long for the small string optimization " + i;
}
const auto size = j.size();
j.emplace("new", std::move(j["0"]));
CHECK(j.size() == size + 1);
CHECK(j["new"] == "a value that is too long for the small string optimization 0");
CHECK(j["0"].is_null());
}
}
#if !defined(JSON_NOEXCEPTION)
SECTION("the container is unchanged if growing it throws")
{
ordered_map<throwing_key, counted> om;
om.reserve(4);
while (om.size() < om.capacity())
{
const auto i = static_cast<int>(om.size());
om.emplace(throwing_key(i), counted(i));
}
const auto size = om.size();
const auto capacity = om.capacity();
// checks that the elements are unchanged
const auto check_unchanged = [&om, size, capacity]
{
CHECK(om.size() == size);
CHECK(om.capacity() == capacity);
int i = 0;
for (const auto& element : om)
{
CHECK(element.first.id == i);
CHECK(element.second.payload == i);
++i;
}
};
SECTION("emplace")
{
// growing copies the existing keys and then the new one; let each of these copies throw
for (std::size_t k = 0; k <= size; ++k)
{
counted value(100);
key_copies_until_throw = static_cast<int>(k);
CHECK_THROWS_AS(om.emplace(throwing_key(100), std::move(value)), std::runtime_error);
key_copies_until_throw = -1;
check_unchanged();
CHECK(value.payload == 100); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
}
om.emplace(throwing_key(100), counted(100));
CHECK(om.size() == size + 1);
CHECK(om.capacity() > capacity);
CHECK(om.at(throwing_key(100)).payload == 100);
}
SECTION("insert(const value_type&)")
{
const std::pair<const throwing_key, counted> value(throwing_key(100), counted(100));
value_copies = 0;
key_copies_until_throw = static_cast<int>(size / 2);
CHECK_THROWS_AS(om.insert(value), std::runtime_error);
key_copies_until_throw = -1;
check_unchanged();
CHECK(value_copies == 0);
}
}
#endif
SECTION("elements that std::vector moves, or that cannot be moved back")
{
SECTION("nothrow move-constructible elements")
{
ordered_map<int, counted> om;
value_copies = 0;
for (int i = 0; i < 100; ++i)
{
om.emplace(i, counted(i));
}
CHECK(om.size() == 100);
CHECK(value_copies == 0);
}
SECTION("mapped type without default constructor")
{
ordered_map<std::string, no_default> om;
for (int i = 0; i < 100; ++i)
{
om.emplace(std::to_string(i), no_default(i));
}
CHECK(om.size() == 100);
int i = 0;
for (const auto& element : om)
{
CHECK(element.first == std::to_string(i));
CHECK(element.second.value == i);
++i;
}
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ if __name__ == '__main__':
namespaces = ['nlohmann']
abi_prefix = 'json_abi'
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics', '_psp', '_snul']
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics', '_psp', '_snul', '_ekmo']
version = '_v' + args.version.replace('.', '_')
inline_namespaces = []