Template Parameter Requirements¶
Class basic_json is configurable through eleven template parameters. The library never formally states what a type passed for one of these parameters has to provide -- the requirements are implied by the way the library uses the resulting object_t, array_t, string_t, etc. This page collects these requirements so they do not have to be discovered by trial and error. Each section lists the concrete types that are known to work for that parameter and the ones that do not, checked against Boost 1.83, Abseil 20250127.0, Folly, EASTL 3.21, ankerl::unordered_dense, phmap, gtl, robin_hood, tsl::ordered_map, and Qt 6.
How to read this page¶
Requirements are split into two groups:
- Always required -- needed to instantiate
basic_jsonat all, or needed by functions that virtually every program uses (construction, element access,dump). - Required for ... -- only needed when a particular part of the API is instantiated. Member function templates are only instantiated when they are used, so a type may be perfectly usable even though it does not satisfy these requirements, as long as the corresponding functions are never called.
Requirements are not checked
Three requirements are checked with a static_assert: the array iterator category, the width of BinaryType's value_type, and NumberUnsignedType being at least as wide as NumberIntegerType. The rest are not diagnosed with dedicated error messages, and violating most of them results in a compiler error somewhere inside the library. Four violations are not caught at compile time at all:
- A
StringTypewhosedata()is not null-terminated compiles and silently misparses numbers, because the lexer hands the buffer tostd::strtoull/std::strtoll/std::strtod. - A stateful
AllocatorTypecompiles and silently ignores its state: allocation, deallocation, andget_allocator()each use a different default-constructed instance. - The two cross-specialization conversions below. These abort on an assertion in a normal build, and only fail silently under
NDEBUG.
Overview¶
| Template parameter | Default | Notable substitutes |
|---|---|---|
ObjectType | std::map | nlohmann::ordered_map, Abseil hash maps |
ArrayType | std::vector | std::deque |
StringType | std::string | std::string-like types over char |
BooleanType | bool | none worth using |
NumberIntegerType | std::int64_t | any signed integer type |
NumberUnsignedType | std::uint64_t | any unsigned integer type at least as wide as NumberIntegerType |
NumberFloatType | double | float (long double: no binary formats) |
AllocatorType | std::allocator | stateless allocators |
JSONSerializer | adl_serializer | serializers with the same interface |
BinaryType | std::vector<std::uint8_t> | std::vector<char> |
CustomBaseClass | void | any default-constructible class |
Third-party containers and incomplete types
object_t is instantiated inside the definition of basic_json -- it is probed for a key_compare member to form object_comparator_t -- i.e. while basic_json is still an incomplete type. std::map is required by the standard to support incomplete mapped types; most third-party maps are not, and inspecting the mapped type at class scope (for instance with std::is_trivially_move_assignable) makes them unusable as ObjectType, no matter how their template arguments are adapted. This rules out absl::btree_map, phmap::btree_map, gtl::btree_map, robin_hood::unordered_node_map, folly::F14FastMap, and eastl::hash_map.
array_t is only named in the class definition and is not instantiated until basic_json is complete, so an ArrayType that inspects its value type at class scope is generally fine -- boost::container::small_vector and static_vector both reject incomplete value types yet work here. absl::InlinedVector is the exception: the std::is_trivially_move_assignable<basic_json> it evaluates while instantiating itself re-enters the library's own trait machinery mid-instantiation.
Folly requires C++20
Folly's headers use consteval and std::type_identity, so any basic_json specialization that names a Folly type has to be compiled as C++20 or later, whatever the rest of the library supports.
ObjectType¶
ObjectType is instantiated as
using object_t = ObjectType<StringType, // key_type
basic_json, // mapped_type
default_object_comparator_t, // key_compare
AllocatorType<std::pair<const StringType,
basic_json>>>; // allocator_type
i.e., the template arguments follow the order and meaning of std::map.
Always required¶
- The template must be usable with four type arguments in the order shown above. The third argument is a comparator; containers that expect something else in this position (e.g., a hash function) need an alias template or wrapper -- see Notes.
- An optional member type
key_compare. If it is present it becomesobject_comparator_t; otherwisedefault_object_comparator_tis used. - Member types
key_type,mapped_type,value_type, anditerator. value_typemust behave likestd::pair<const key_type, mapped_type>; the library accesses.firstand.secondon it.iteratormust be default-constructible and satisfy LegacyBidirectionalIterator. The type returned bycbegin()/cend()must satisfy the same requirements.- Constructors: default, copy, move, and from an iterator range
(first, last). - Member functions
begin(),end(),cbegin(),cend(),empty(),size(),max_size(),clear(),find(key),count(key),emplace(key, value),insert(value_type),insert(first, last),operator[](key),erase(iterator), anderase(first, last).erase(iterator)may return the following iterator orvoid; in the latter case the library computes the successor itself, before erasing. erase(key)is optional: if the container does not provide one, the library falls back tofind(key)followed byerase(iterator).at(key)is required only byto_ubjsonandto_bjdata, but every container tried here provides it.emplaceandinsert(value_type)must returnstd::pair<iterator, bool>and must have unique-key semantics; multimaps cannot be used.- The type must be swappable (via
std::swapor an ADLswap). - The comparison operators
==and<;!=,<=,>, and>=are derived from them. Where the library uses three-way comparison (C++20),==and<=>are required instead -- the six two-way operators do not satisfy it. They implementbasic_json's comparison operators.
Required for heterogeneous key lookup¶
The overloads of at, operator[], find, contains, count, erase, and value that accept a key type other than object_t::key_type require
- a transparent comparator, i.e.
object_comparator_thas a member typeis_transparent(this is why the default comparator isstd::less<>since C++14), and - corresponding heterogeneous
find,count,erase, andoperator[]overloads on the container.
Notes¶
std::unordered_map needs an adapter¶
std::unordered_map cannot be passed directly: its third template parameter is a hash function, but basic_json passes a comparator in that position. An alias template or wrapper that restores the expected argument order makes it usable:
template<class Key, class T, class IgnoredCompare, class Allocator>
struct unordered_map_object
: std::unordered_map<Key, T, std::hash<Key>, std::equal_to<Key>, Allocator>
{
using base_t = std::unordered_map<Key, T, std::hash<Key>, std::equal_to<Key>, Allocator>;
using base_t::base_t;
};
using unordered_json = nlohmann::basic_json<unordered_map_object>;
Whether std::unordered_map can be instantiated at all depends on the standard library: object_t is formed while basic_json is still incomplete (see the warning above), and libstdc++ 9 needs the size of the mapped type to instantiate the hash map's node type, so the adapter does not compile there. Newer libstdc++ versions, and the hash maps listed below, do not have that problem.
The adapter above works verbatim for Abseil's, Boost's, phmap's and gtl's hash maps, which all place the hash function third and take a std::pair<const Key, T> allocator fifth. Two need a different adapter:
ankerl::unordered_denseexpects an allocator overstd::pair<Key, T>(non-const key), so the allocator has to be rebound to that or dropped.robin_hood's fifth parameter is the non-typeMaxLoadFactor100, so its adapter must drop the allocator entirely.
None of these hash maps defines key_compare, so all of them additionally rely on object_comparator_t falling back to default_object_comparator_t; see object_comparator_t.
Abseil hash maps¶
absl::flat_hash_map and absl::node_hash_map tolerate an incomplete value type, but they take a hash function as their third template argument. The same adapter as for std::unordered_map makes them usable:
template<class Key, class T, class IgnoredCompare, class Allocator>
struct flat_hash_object
: absl::flat_hash_map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>
{
using base_t = absl::flat_hash_map<Key, T, absl::Hash<Key>, std::equal_to<Key>, Allocator>;
using base_t::base_t;
};
using flat_hash_json = nlohmann::basic_json<flat_hash_object>;
absl::node_hash_map keeps references to the mapped values valid across insertions; absl::flat_hash_map does not, which makes it behave like ordered_json with respect to iterator invalidation. Both expose a capacity() member function, so JSON_DIAGNOSTICS treats them conservatively and keeps the parent pointers correct either way.
Iteration order¶
The library never relies on the container's iteration order for correctness; it does determine the order in which object keys are serialized by dump and visited by items. See Object Order.
capacity() marks a container as insertion-ordered¶
With JSON_DIAGNOSTICS enabled, the library detects insertion-ordered maps by probing for a capacity() member function (nlohmann::ordered_map inherits it from std::vector) and refreshes all parent pointers after every insertion. An ObjectType that happens to have a capacity() member is therefore treated conservatively -- this is correct, but slower.
Key order and duplicate keys¶
The library does not sort or de-duplicate keys itself; the behavior described in object_t is entirely the behavior of the chosen container.
Reference implementation
docs/mkdocs/docs/examples/custom_object_type.hpp wraps a private std::map and satisfies every requirement above. It does not define key_compare, so object_comparator_t falls back to default_object_comparator_t -- a good starting point for a custom ObjectType.
#pragma once
#include <map>
#include <utility>
// A minimal, self-contained ObjectType built around a private std::map.
// key_compare is deliberately not exposed: when an ObjectType has no
// key_compare member, the library falls back to its own default comparator.
// See https://json.nlohmann.me/features/types/template_parameters/#objecttype
template<class Key, class T, class Compare, class Allocator>
class custom_object_type
{
using map_t = std::map<Key, T, Compare, Allocator>;
map_t data_;
public:
using key_type = typename map_t::key_type;
using mapped_type = typename map_t::mapped_type;
using value_type = typename map_t::value_type;
using size_type = typename map_t::size_type;
using iterator = typename map_t::iterator;
using const_iterator = typename map_t::const_iterator;
custom_object_type() = default;
custom_object_type(const custom_object_type&) = default;
custom_object_type(custom_object_type&&) = default;
custom_object_type& operator=(const custom_object_type&) = default;
custom_object_type& operator=(custom_object_type&&) = default;
template<class InputIt>
custom_object_type(InputIt first, InputIt last) : data_(first, last) {}
iterator begin()
{
return data_.begin();
}
iterator end()
{
return data_.end();
}
const_iterator begin() const
{
return data_.begin();
}
const_iterator end() const
{
return data_.end();
}
const_iterator cbegin() const
{
return data_.cbegin();
}
const_iterator cend() const
{
return data_.cend();
}
bool empty() const
{
return data_.empty();
}
size_type size() const
{
return data_.size();
}
size_type max_size() const
{
return data_.max_size();
}
void clear()
{
data_.clear();
}
iterator find(const key_type& key)
{
return data_.find(key);
}
const_iterator find(const key_type& key) const
{
return data_.find(key);
}
size_type count(const key_type& key) const
{
return data_.count(key);
}
std::pair<iterator, bool> emplace(const key_type& key, const mapped_type& value)
{
return data_.emplace(key, value);
}
std::pair<iterator, bool> insert(const value_type& value)
{
return data_.insert(value);
}
template<class InputIt>
void insert(InputIt first, InputIt last)
{
data_.insert(first, last);
}
mapped_type& operator[](const key_type& key)
{
return data_[key];
}
mapped_type& at(const key_type& key)
{
return data_.at(key);
}
const mapped_type& at(const key_type& key) const
{
return data_.at(key);
}
iterator erase(iterator pos)
{
return data_.erase(pos);
}
iterator erase(iterator first, iterator last)
{
return data_.erase(first, last);
}
size_type erase(const key_type& key)
{
return data_.erase(key);
}
void swap(custom_object_type& other)
{
data_.swap(other.data_);
}
friend bool operator==(const custom_object_type& lhs, const custom_object_type& rhs)
{
return lhs.data_ == rhs.data_;
}
friend bool operator<(const custom_object_type& lhs, const custom_object_type& rhs)
{
return lhs.data_ < rhs.data_;
}
};
Compiling and using it
#include <iostream>
#include <type_traits>
#include <vector>
#include <nlohmann/json.hpp>
#include "custom_object_type.hpp"
using custom_json = nlohmann::basic_json<custom_object_type, std::vector>;
int main()
{
custom_json j;
j["pi"] = 3.141;
j["happy"] = true;
j["list"] = {1, 2, 3};
std::cout << j.dump(2) << std::endl;
std::cout << std::boolalpha << (custom_json::parse(j.dump()) == j) << std::endl;
// custom_object_type has no key_compare member, so object_comparator_t
// falls back to its default
std::cout << std::boolalpha
<< std::is_same<custom_json::object_comparator_t, custom_json::default_object_comparator_t>::value
<< std::endl;
}
Output:
{
"happy": true,
"list": [
1,
2,
3
],
"pi": 3.141
}
true
true
Compatible containers¶
| Container | Notes |
|---|---|
std::map (default) | |
nlohmann::ordered_map | used by ordered_json; keeps insertion order |
nlohmann::fifo_map | keeps insertion order; adapter puts fifo_map_compare in the comparator slot |
boost::container::map, boost::container::flat_map | no adapter needed |
std::unordered_map | through the adapter above; not with libstdc++ 9, see the note |
boost::unordered_map, boost::unordered_flat_map, boost::unordered_node_map | through the adapter above |
absl::flat_hash_map, absl::node_hash_map | through the adapter above; flat_hash_map moves mapped values on rehash |
phmap::flat_hash_map, phmap::node_hash_map, gtl::flat_hash_map | through the adapter above |
ankerl::unordered_dense::map and segmented_map | adapter must rebind or drop the allocator |
robin_hood::unordered_flat_map | adapter must drop the allocator |
folly::F14NodeMap | through the adapter above; requires C++20, see the note above |
folly::sorted_vector_map | alias must drop the allocator, whose value type it disagrees on |
Containers that cannot be used¶
| Container | Reason |
|---|---|
absl::btree_map, phmap::btree_map, gtl::btree_map | require a complete mapped type |
robin_hood::unordered_node_map, folly::F14FastMap, eastl::hash_map | require a complete mapped type |
eastl::map | EASTL iterators do not work with std::iterator_traits |
tsl::ordered_map | its iterators expose the mapped value as const |
QMap | no value_type member type |
QHash | its value_type is the mapped type rather than a key/value pair, and its iterators dereference to the mapped value |
std::multimap, std::unordered_multimap | emplace does not return std::pair<iterator, bool> |
ArrayType¶
ArrayType is instantiated as
using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
Always required¶
- The template must be usable with two type arguments (value type and allocator).
- Member types
value_typeanditerator. - Constructors: default, copy, and move; and from an iterator range
(first, last). - Member functions
begin(),end(),cbegin(),cend(),empty(),size(),max_size(),clear(),operator[](size_type),back(),push_back(),emplace_back(),pop_back(),resize(),insert()(single element, count, and range),erase(pos), anderase(first, last).basic_json::insert(pos, initializer_list)goes through the range overload, so no initializer-listinsertis needed.at(size_type)is not required:basic_json::at(size_type)checks the index itself and then usesoperator[]. iteratormust be default-constructible, and it as well as the type returned bycbegin()/cend()must satisfy LegacyRandomAccessIterator. Astatic_assertonly checks for LegacyBidirectionalIterator, butdump(cend() - 1),erase(idx)(begin() + idx), and the random-access operations ofbasic_json::iteratorrequire random access.- The comparison operators, as for
ObjectType:==and<, or==and<=>under C++20.
Required for individual functions¶
- A member type
value_type, forto_bsonof an array. - A constructor from
(count, value), forbasic_json(size_type, const basic_json&). - Swappability, via
std::swapor an ADLswap, forswap(array_t&).
capacity() is optional
With JSON_DIAGNOSTICS enabled, the library reads array_t::capacity() to find out whether adding an element reallocated the array and moved its elements, which would invalidate the parent pointers. An array type without a capacity() member function is handled conservatively: the parent pointers of all elements are refreshed after every insertion, which makes adding n elements cost O(*n*²). Only diagnostics builds pay this; without them capacity() is never called.
Reference implementation
docs/mkdocs/docs/examples/custom_array_type.hpp wraps a private std::vector and satisfies every requirement above -- a good starting point for a custom ArrayType.
#pragma once
#include <memory>
#include <utility>
#include <vector>
// A minimal, self-contained ArrayType built around a private std::vector.
// See https://json.nlohmann.me/features/types/template_parameters/#arraytype
template<class T, class Allocator = std::allocator<T>>
class custom_array_type
{
using vector_t = std::vector<T, Allocator>;
vector_t data_;
public:
using value_type = typename vector_t::value_type;
using size_type = typename vector_t::size_type;
using iterator = typename vector_t::iterator;
using const_iterator = typename vector_t::const_iterator;
custom_array_type() = default;
custom_array_type(const custom_array_type&) = default;
custom_array_type(custom_array_type&&) = default;
custom_array_type& operator=(const custom_array_type&) = default;
custom_array_type& operator=(custom_array_type&&) = default;
template<class InputIt>
custom_array_type(InputIt first, InputIt last) : data_(first, last) {}
custom_array_type(size_type count, const T& value) : data_(count, value) {}
iterator begin()
{
return data_.begin();
}
iterator end()
{
return data_.end();
}
const_iterator begin() const
{
return data_.begin();
}
const_iterator end() const
{
return data_.end();
}
const_iterator cbegin() const
{
return data_.cbegin();
}
const_iterator cend() const
{
return data_.cend();
}
bool empty() const
{
return data_.empty();
}
size_type size() const
{
return data_.size();
}
size_type max_size() const
{
return data_.max_size();
}
void clear()
{
data_.clear();
}
void resize(size_type n)
{
data_.resize(n);
}
T& operator[](size_type pos)
{
return data_[pos];
}
const T& operator[](size_type pos) const
{
return data_[pos];
}
T& back()
{
return data_.back();
}
const T& back() const
{
return data_.back();
}
void push_back(const T& value)
{
data_.push_back(value);
}
void push_back(T&& value)
{
data_.push_back(std::move(value));
}
template<class... Args>
void emplace_back(Args&& ... args)
{
data_.emplace_back(std::forward<Args>(args)...);
}
void pop_back()
{
data_.pop_back();
}
iterator insert(const_iterator pos, const T& value)
{
return data_.insert(pos, value);
}
iterator insert(const_iterator pos, size_type count, const T& value)
{
return data_.insert(pos, count, value);
}
template<class InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last)
{
return data_.insert(pos, first, last);
}
iterator erase(const_iterator pos)
{
return data_.erase(pos);
}
iterator erase(const_iterator first, const_iterator last)
{
return data_.erase(first, last);
}
void swap(custom_array_type& other)
{
data_.swap(other.data_);
}
friend bool operator==(const custom_array_type& lhs, const custom_array_type& rhs)
{
return lhs.data_ == rhs.data_;
}
friend bool operator<(const custom_array_type& lhs, const custom_array_type& rhs)
{
return lhs.data_ < rhs.data_;
}
};
Compiling and using it
#include <iostream>
#include <map>
#include <nlohmann/json.hpp>
#include "custom_array_type.hpp"
using custom_json = nlohmann::basic_json<std::map, custom_array_type>;
int main()
{
custom_json j = custom_json::array();
j.push_back(1);
j.push_back(2);
j.push_back(3);
std::cout << j.dump() << std::endl;
std::cout << std::boolalpha << (custom_json::parse(j.dump()) == j) << std::endl;
}
Output:
[1,2,3]
true
Compatible containers¶
| Container | Notes |
|---|---|
std::vector (default) | |
std::deque | references survive appends, but not insertions elsewhere; see the capacity() note above |
std::pmr::vector | through an alias, as the allocator comes from AllocatorType instead |
boost::container::vector, deque, devector | |
boost::container::stable_vector | the only one tried that keeps references valid across every insertion |
boost::container::small_vector, folly::small_vector | through an alias that fixes the inline capacity |
boost::container::static_vector | through the same kind of alias, for arrays that stay within the fixed capacity |
folly::fbvector | requires C++20, see the note above |
Containers that cannot be used¶
| Container | Reason |
|---|---|
std::list | no operator[], and no random-access iterators |
eastl::vector, QList, QVector | no max_size(); they handle the incomplete value type fine |
absl::InlinedVector | requires a complete value type, see the note above |
absl::FixedArray | the size is fixed at construction, so resize, push_back, insert and erase are missing |
StringType¶
StringType is used both for JSON string values and for the keys of JSON objects (string_t and object_t::key_type).
Always required¶
- A member type
value_typethat is one byte wide andchar-compatible. The library stores and processes UTF-8 encodedchardata and handsdata()tostd::strtoull/std::strtoll.std::wstring,std::u16string, andstd::u32stringare not valid choices; see the FAQ on wide string handling. - Constructors: default, copy, move, from
const char*(which must not beexplicit), from(const char*, size_type), and from(size_type, char); and copy or move assignment. - Member functions
size(),clear(),resize(n, c),data(),push_back(char), andoperator[](const and non-const, returning references).c_str()andback()are not required. data()must return a pointer to a contiguous, null-terminated buffer -- the parser hands it tostd::strtoull. A type whosedata()is not null-terminated does not fail to compile; it silently misparses numbers.append(const char*, size_type), used bydump, andappend(const StringType&), used by the CBOR reader for indefinite-length strings. The library's internal string concatenation additionally has to append acharand aconst char*; for each it selects betweenappend(arg),operator+=,append(first, last), andappend(data, size).- The comparison operator
==against anotherStringType, and<for use as a key of the chosenObjectType(with the default comparator,std::less<>must be able to compare twoStringTypevalues, and aStringTypewith the key types used for lookup).!=is never applied to aStringType, and==againstconst char*is resolved by the implicitconst char*constructor.
Required for the binary formats¶
resize(n), used by the readers to make room for a block of bytes.- Non-const
operator[], into which the readersstd::memcpythose bytes. A non-constdata()would serve just as well, butstd::stringhas only had one since C++17, and the library still supports C++11.
Required for JSON Pointer, flatten, and diff¶
- A static member
nposand the member functionfind_first_of(char, size_type)-- together withdata(),reserve(n), andappend(const char*, size_type)they implement the escaping and unescaping of reference tokens described in RFC 6901. Neitherfind(const StringType&, size_type), norsubstr(pos, count), norreplace(pos, count, const StringType&)is required. empty().begin()andend()-- used byoperator[](const json_pointer&)to decide whether a reference token denotes an array index.
Required for other functionality¶
| Functionality | Additional requirement |
|---|---|
diff, items, std::hash | conversion of a std::size_t to StringType: either assignability from the result of std::to_string, or an ADL overload void int_to_string(StringType&, std::size_t) |
std::hash<basic_json> | additionally a specialization of std::hash<StringType> |
to_bson | find(value_type) and npos |
parse from a string_t | the input adapters must accept it; otherwise pass a character range |
operator<<(std::ostream&, const json_pointer&) | streamability to std::ostream |
| exception messages | data() and size(), or begin() and end() |
Compatible types¶
| Type | Notes |
|---|---|
std::string (default) | |
std::basic_string with a custom stateless allocator | |
std::pmr::string | see the warning below before relying on the memory resource |
boost::container::string | needs a user-supplied std::hash specialization (Boost provides boost::hash instead) |
folly::fbstring | requires C++20, see the note above |
eastl::string | needs a user-supplied std::hash and an ADL int_to_string (it is not assignable from a std::string); parse does not accept it directly -- pass a character range or a std::string |
| a custom string class in a user-defined namespace | if the requirements above are met |
Types that cannot be used¶
| Type | Reason |
|---|---|
std::wstring, std::u16string, std::u32string | the character type is not one byte wide |
std::u8string | one byte wide, but char8_t is not char-compatible |
absl::Cord | no value_type, and the storage is not contiguous |
QString | no append(const char*, size_type); its QChar is also two bytes wide, though that is never diagnosed |
A std::pmr::string mostly does not use the memory resource you choose
basic_json cannot be given an allocator or a memory resource. AllocatorType is default-constructed at every allocation and has to be stateless (see AllocatorType), and string values the library creates are constructed with their own default allocator. So:
- Every string the library itself produces -- from
parse, fromdump, or by default construction -- allocates fromstd::pmr::get_default_resource(). - Copying an arena-backed string into a value silently drops its memory resource: the copy lands on the default resource, because
std::pmr::polymorphic_allocatordoes not propagate on copy construction. Nothing warns about this. - Moving one in does keep it, and later growth still allocates from that arena -- but it does not survive a copy of the enclosing
basic_json. - Passing
std::pmr::polymorphic_allocatorasAllocatorTypedoes not work around any of this; it does not compile.
Apart from moving a string in, the only way to redirect these allocations is the process-global std::pmr::set_default_resource().
Reference implementation
docs/mkdocs/docs/examples/custom_string_type.hpp wraps a private std::string and satisfies every requirement above -- a good starting point for a custom StringType. The unit test tests/src/unit-alt-string.cpp contains a more thorough variant, alt_string, exercised against a larger part of the API.
#pragma once
#include <ostream>
#include <string>
// A minimal, self-contained StringType built around a private std::string.
// Wraps rather than inherits, so it exposes exactly what the library needs
// and nothing more of std::string's interface.
//
// Covers the "Always required" members, the extras needed for the binary
// formats, and the extras needed for JSON Pointer / flatten / unflatten /
// diff. Extending it further (e.g. for std::hash<basic_json> or to_bson) is
// a matter of adding the extra members listed in the "Required for other
// functionality" table.
//
// See https://json.nlohmann.me/features/types/template_parameters/#stringtype
class custom_string_type
{
std::string data_;
public:
using value_type = char;
using size_type = std::string::size_type;
using iterator = std::string::iterator;
using const_iterator = std::string::const_iterator;
static constexpr size_type npos = std::string::npos;
custom_string_type() = default;
custom_string_type(const custom_string_type&) = default;
custom_string_type(custom_string_type&&) = default;
custom_string_type& operator=(const custom_string_type&) = default;
custom_string_type& operator=(custom_string_type&&) = default;
// not explicit: the library relies on being able to hand it a string literal
custom_string_type(const char* s) : data_(s) {}
custom_string_type(const char* s, size_type count) : data_(s, count) {}
custom_string_type(size_type count, char ch) : data_(count, ch) {}
size_type size() const
{
return data_.size();
}
bool empty() const
{
return data_.empty();
}
void clear()
{
data_.clear();
}
void resize(size_type n)
{
data_.resize(n);
}
void resize(size_type n, char c)
{
data_.resize(n, c);
}
void reserve(size_type n)
{
data_.reserve(n);
}
// must stay null-terminated -- the parser hands this to std::strtoull &
// friends; std::string::data() has guaranteed that since C++11
const char* data() const
{
return data_.data();
}
void push_back(char c)
{
data_.push_back(c);
}
char& operator[](size_type pos)
{
return data_[pos];
}
char operator[](size_type pos) const
{
return data_[pos];
}
custom_string_type& append(const char* s, size_type count)
{
data_.append(s, count);
return *this;
}
custom_string_type& append(const custom_string_type& other)
{
data_.append(other.data_);
return *this;
}
size_type find_first_of(char c, size_type pos = 0) const
{
return data_.find_first_of(c, pos);
}
iterator begin()
{
return data_.begin();
}
iterator end()
{
return data_.end();
}
const_iterator begin() const
{
return data_.begin();
}
const_iterator end() const
{
return data_.end();
}
friend bool operator==(const custom_string_type& lhs, const custom_string_type& rhs)
{
return lhs.data_ == rhs.data_;
}
friend bool operator<(const custom_string_type& lhs, const custom_string_type& rhs)
{
return lhs.data_ < rhs.data_;
}
// not required by the library itself, but dump() returns a custom_string_type
// and this makes `std::cout << j.dump()` work as expected
friend std::ostream& operator<<(std::ostream& os, const custom_string_type& s)
{
return os << s.data_;
}
};
Compiling and using it
#include <iostream>
#include <map>
#include <vector>
#include <nlohmann/json.hpp>
#include "custom_string_type.hpp"
using custom_json = nlohmann::basic_json<std::map, std::vector, custom_string_type>;
int main()
{
custom_json j;
j["pi"] = 3.141;
j["happy"] = true;
j["list"] = {1, 2, 3};
std::cout << j.dump(2) << std::endl;
std::cout << std::boolalpha << (custom_json::parse(j.dump()) == j) << std::endl;
}
Output:
{
"happy": true,
"list": [
1,
2,
3
],
"pi": 3.141
}
true
BooleanType¶
boolean_t is stored directly inside basic_json, as a member of an anonymous union.
Always required¶
- A literal type that is trivially default-constructible, trivially copyable, and trivially destructible; otherwise the union's special member functions are deleted.
- Implicitly convertible from
bool-- anexplicitconstructor is not enough, because theto_jsonoverload for a customBooleanTypeis constrained onstd::is_convertible-- and contextually convertible tobool(here anexplicit operator boolis fine). - Comparison operators
==,!=,<,<=,>,>=(or<=>). - Convertible from and to
boolthrough the serializer, becauseget<bool>()is used internally.
There is little reason to use anything other than bool here.
Compatible types¶
bool is the only usable choice. Another trivially copyable type that is implicitly convertible to and from bool -- std::uint8_t, say -- does compile, and JSON booleans still round-trip, but the type then serves as both boolean_t and an ordinary integer: basic_json can no longer be constructed or assigned from a std::uint8_t at all (the boolean and unsigned-integer to_json overloads become ambiguous), and get<std::uint8_t>() on a number throws type_error.302 instead of returning the value.
NumberIntegerType and NumberUnsignedType¶
Both types are stored directly inside basic_json's union.
Always required¶
std::is_integralmust be satisfied:NumberIntegerTypemust be a signed integer type,NumberUnsignedTypean unsigned integer type. Class types are not supported -- among others, the constructors taking integer values are constrained onstd::is_integral.- Trivially default-constructible, trivially copyable, and trivially destructible (union member).
std::numeric_limitsmust be specialized for both types.NumberUnsignedTypemust be able to represent the absolute value of everyNumberIntegerTypevalue; serialization of negative numbers converts the value toNumberUnsignedType. Astatic_assertrequires it to be at least as wide asNumberIntegerType, which is what that amounts to for the standard integer types.- Both types must fit into the internal 64-character number buffer used by
dump, which is the case for all standard integer types. std::hash<basic_json>additionally requiresstd::hashspecializations.
Notes¶
The number types influence what the parser accepts: an integer literal that does not round-trip through the chosen type is stored as number_float_t instead. Choosing types narrower than 64 bits therefore silently changes parse results rather than raising an error. See Number Handling for details.
Compatible types¶
| Type pair | Support |
|---|---|
std::int64_t / std::uint64_t (default) | full |
std::int32_t / std::uint32_t, long long / unsigned long long | full; narrower types change which literals the parser can represent |
| any other pair of standard signed/unsigned integer types | full |
| class types, enumerations | not usable; std::is_integral must hold |
bool, or a type already used for another member of the union | not usable; std::is_integral<bool> is in fact true, but the get_impl_ptr overloads for boolean_t, number_integer_t, number_unsigned_t and number_float_t would collide |
NumberFloatType¶
number_float_t is stored directly inside basic_json's union.
Always required¶
- Trivially default-constructible, trivially copyable, and trivially destructible (union member).
std::numeric_limitsmust be specialized;max_digits10is used to size the conversion.std::isfinitemust be applicable to the type.
Required for parsing and serialization¶
NumberFloatType must be one of float, double, or long double:
- The parser converts number literals with
std::strtof,std::strtod, orstd::strtold; the library provides overloads for exactly these three types. dumpfalls back tostd::snprintfwith the%gand%Lgconversion specifiers, for which the library likewise provides onlydoubleandlong doubleoverloads (floatis promoted todouble).
If std::numeric_limits<NumberFloatType> describes an IEEE 754 binary32 or binary64 number, dump uses the Grisu2 algorithm, which produces the shortest representation that round-trips. Otherwise the snprintf fallback with max_digits10 digits is used.
Required for the binary formats¶
NumberFloatType must be float or double. The writers for CBOR, MessagePack, UBJSON, BJData, and BSON map a floating-point value onto an IEEE 754 binary32 or binary64 field and have no encoding for long double.
Compatible types¶
| Type | Support |
|---|---|
double (default) | full; short round-trip output through Grisu2 |
float | full; short round-trip output through Grisu2 |
long double | dump and parse only; the binary format writers do not compile, as they only handle IEEE 754 binary32 and binary64 |
| any other type | not usable |
AllocatorType¶
AllocatorType is instantiated with one argument, for each of object_t, array_t, string_t, binary_t, basic_json, and std::pair<const StringType, basic_json>.
Always required¶
- The template must be usable with exactly one type argument. The library instantiates
AllocatorType<T>directly and never usesstd::allocator_traits<...>::rebind_alloc. - It must satisfy the Allocator named requirement so that
std::allocator_traitscan be used with it. - It must be default-constructible and stateless. Objects are allocated with a default-constructed allocator and deallocated with a different default-constructed allocator, and
get_allocator()returns a default-constructed instance. Allocators carrying state are not supported, so there is no way to tell abasic_jsonwhere to allocate from; see the note underStringTypefor what that means in practice. A stateful allocator is not diagnosed: it compiles and silently ignores the state. - It must support incomplete types:
AllocatorType<basic_json>is instantiated inside the definition ofbasic_jsonitself. std::allocator_traits<AllocatorType<basic_json>>::pointerbecomesbasic_json::pointer, and iterators are constructed from rawbasic_json*values. Thepointertype must therefore be a plain pointer; fancy pointers are not supported.
Compatible types¶
| Type | Support |
|---|---|
std::allocator (default) | full |
| a custom stateless allocator template | full |
stateful allocators, e.g. std::pmr::polymorphic_allocator | not usable; see the requirements above |
JSONSerializer¶
JSONSerializer is instantiated as JSONSerializer<T, void> and defaults to adl_serializer.
Always required¶
- The template must accept two type arguments. It does not have to give the second one a default --
basic_jsondeclares the parameter astemplate<typename T, typename SFINAE = void> class JSONSerializer, so uses such asJSONSerializer<T>inside the library supplyvoidthemselves. The second parameter exists so that partial specializations can be constrained by SFINAE. - For every type
Tthat is converted to a JSON value, a static member functionstatic void to_json(basic_json&, T)must exist. - For every type
Tthat is converted from a JSON value, eitherstatic void from_json(const basic_json&, T&)orstatic T from_json(const basic_json&)must exist. The latter form is required for types that are not default-constructible; see Arbitrary Types Conversions. - To support the converting constructor between different
basic_jsonspecializations,to_jsonmust be available forboolean_t,number_integer_t,number_unsigned_t,number_float_t,string_t,object_t,array_t, andbinary_tof the source specialization.
Compatible types¶
| Type | Support |
|---|---|
nlohmann::adl_serializer (default) | full |
a class template deriving from adl_serializer | full; the usual way to change behavior while keeping the defaults |
| an unrelated template with the same interface | full, but it has to handle every type the library converts |
BinaryType¶
BinaryType is not a JSON type; it is used for the byte strings of the binary formats. It is wrapped as
using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;
Always required¶
- A non-
finalclass type --byte_container_with_subtypederives from it publicly. - A member type
value_typethat is exactly one byte wide (e.g.,std::uint8_t,char, orstd::byte). Readers and writers reinterpret the container's storage as raw bytes, so a widervalue_typeis rejected with astatic_assert. - Contiguous storage: the binary readers
std::memcpyinto&binary[n], the writersreinterpret_castdata().data() + nwould do for the readers too, but they share one helper withStringType, whose non-constdata()is C++17 and later only. - Default-constructible, copy-constructible, and move-constructible.
- Member functions
size(),empty(),data(),resize(),operator[],back(),begin(),end(),cbegin(), andcend()with random-access iterators, andinsert(pos, first, last), which the CBOR reader uses to join the chunks of an indefinite-length byte string.push_back()is not required. - Comparison operators:
==is used bybyte_container_with_subtype, the relational operators bybasic_json's comparison operators.
Required for individual functions¶
clear(), forbasic_json::clear().
max_size(), at(), reserve(), erase(), pop_back(), and emplace_back() are not used at all.
See binary_t for how a non-default BinaryType changes the meaning of assigning such a container to a basic_json value.
Reference implementation
docs/mkdocs/docs/examples/custom_binary_type.hpp wraps a private std::vector<std::uint8_t> and satisfies every requirement above -- a good starting point for a custom BinaryType.
#pragma once
#include <cstdint>
#include <initializer_list>
#include <vector>
// A minimal, self-contained BinaryType built around a private std::vector.
// See https://json.nlohmann.me/features/types/template_parameters/#binarytype
class custom_binary_type
{
using vector_t = std::vector<std::uint8_t>;
vector_t data_;
public:
using value_type = vector_t::value_type;
using size_type = vector_t::size_type;
using iterator = vector_t::iterator;
using const_iterator = vector_t::const_iterator;
custom_binary_type() = default;
custom_binary_type(const custom_binary_type&) = default;
custom_binary_type(custom_binary_type&&) = default;
custom_binary_type& operator=(const custom_binary_type&) = default;
custom_binary_type& operator=(custom_binary_type&&) = default;
template<class InputIt>
custom_binary_type(InputIt first, InputIt last) : data_(first, last) {}
// so basic_json::binary({0x01, 0x02}) can build one directly
custom_binary_type(std::initializer_list<std::uint8_t> init) : data_(init) {}
size_type size() const
{
return data_.size();
}
bool empty() const
{
return data_.empty();
}
void clear()
{
data_.clear();
}
void resize(size_type n)
{
data_.resize(n);
}
// read-only is enough: the writers only ever read from a binary value
const std::uint8_t* data() const
{
return data_.data();
}
std::uint8_t& operator[](size_type pos)
{
return data_[pos];
}
std::uint8_t operator[](size_type pos) const
{
return data_[pos];
}
std::uint8_t& back()
{
return data_.back();
}
std::uint8_t back() const
{
return data_.back();
}
iterator begin()
{
return data_.begin();
}
iterator end()
{
return data_.end();
}
const_iterator begin() const
{
return data_.begin();
}
const_iterator end() const
{
return data_.end();
}
const_iterator cbegin() const
{
return data_.cbegin();
}
const_iterator cend() const
{
return data_.cend();
}
template<class InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last)
{
return data_.insert(pos, first, last);
}
friend bool operator==(const custom_binary_type& lhs, const custom_binary_type& rhs)
{
return lhs.data_ == rhs.data_;
}
friend bool operator<(const custom_binary_type& lhs, const custom_binary_type& rhs)
{
return lhs.data_ < rhs.data_;
}
};
Compiling and using it
#include <cstdint>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "custom_binary_type.hpp"
using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
std::int64_t, std::uint64_t, double, std::allocator,
nlohmann::adl_serializer, custom_binary_type>;
int main()
{
const auto j = custom_json::binary({0x01, 0x02, 0x03});
std::cout << j.dump() << std::endl;
std::cout << std::boolalpha << (custom_json::from_cbor(custom_json::to_cbor(j)) == j) << std::endl;
}
Output:
{"bytes":[1,2,3],"subtype":null}
true
Compatible containers¶
| Container | Notes |
|---|---|
std::vector<std::uint8_t> (default) | |
std::vector<char>, std::vector<std::byte> | dump() writes the bytes as 0..255 whichever is used |
boost::container::vector<std::uint8_t>, boost::container::small_vector<std::uint8_t, N> | |
absl::InlinedVector<std::uint8_t, N> | usable here, unlike as an ArrayType, because the value type is complete |
eastl::vector<std::uint8_t> | usable here, unlike as an ArrayType, because max_size() is not needed |
folly::fbvector<std::uint8_t> | requires C++20, see the note above |
Containers that cannot be used¶
| Container | Reason |
|---|---|
QByteArray | no empty() (it spells that isEmpty()); its insert takes an index rather than an iterator; and it converts to string_t, which makes to_json ambiguous between a string and a binary value |
std::string | binary_t::container_type and string_t would be the same type, so the two swap overloads collide and basic_json cannot be instantiated at all |
std::deque<std::uint8_t> | storage is not contiguous, so there is no data() |
containers whose value_type is wider than one byte | see above -- accepted by the compiler, wrong at runtime |
CustomBaseClass¶
CustomBaseClass is an extension point: unless it is void (the default, which selects the empty nlohmann::json_default_base), basic_json publicly derives from it.
Always required¶
- A non-
final, default-constructible class type. basic_jsonis copy-/move-constructible and copy-/move-assignable only ifCustomBaseClassis.
Notes¶
basic_json is documented to be a StandardLayoutType. Because basic_json has non-static data members of its own, a CustomBaseClass with non-static data members forfeits this guarantee.
Note the namespace of CustomBaseClass becomes an associated namespace of basic_json for the purpose of argument-dependent lookup.
See json_base_class_t for an example.
Compatible types¶
| Type | Support |
|---|---|
void (default) | an empty base class is used; no effect on basic_json |
any default-constructible, non-final class | full; see json_base_class_t |
Cross-specialization conversions¶
Converting a value from one basic_json specialization into another (see the converting constructor) imposes two additional requirements that are not diagnosed at compile time. With assertions enabled they abort on the JSON_ASSERT at the end of the converting constructor; under NDEBUG they fail silently at runtime:
- The target
string_tmust be directly constructible from the sourcestring_t. Otherwise the string is converted to an array of character codes. - The target
object_t::key_typemust be directly constructible from the source object's key type. Otherwise the object is converted to an array of key/value pairs.
See issue #3425, string_t, and object_t.
See also¶
- Types -- overview of how JSON values are stored
- Number Handling -- how the number types affect parsing and serialization
- Object Order -- using an insertion-ordered
ObjectType basic_json-- API documentation of the class template