mirror of
https://github.com/nlohmann/json.git
synced 2026-10-12 01:17:14 +00:00
A std::pair or std::tuple whose every element is itself a two-element array with a string first element (e.g. std::pair<std::string, int>) serializes to a JSON object instead of a JSON array, because to_json builds the value with a brace initializer and the initializer-list object-detection rule fires. The resulting object cannot be read back into the original type and collapses duplicate keys. Document this quirk in the conversions guide, together with the unaffected cases and the idiom to force an array. Claude-Session: https://claude.ai/code/session_016cwQq8WQRFzQcGQtbJTtJg Signed-off-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
# Converting values
|
|
|
|
A `basic_json` value stores JSON data, but most of the time you want to move that data into ordinary C++ types (an
|
|
`#!cpp int`, a `#!cpp std::string`, a `#!cpp std::vector`, or one of your own structs) and back. This page describes how
|
|
these conversions work.
|
|
|
|
## Getting values out
|
|
|
|
The [`get`](../api/basic_json/get.md) function template returns a copy of the stored value converted to the requested
|
|
type:
|
|
|
|
```cpp
|
|
json j = R"({"name": "Mary", "age": 42, "hobbies": ["hiking", "reading"]})"_json;
|
|
|
|
auto name = j["name"].get<std::string>(); // "Mary"
|
|
auto age = j["age"].get<int>(); // 42
|
|
auto hobbies = j["hobbies"].get<std::vector<std::string>>(); // {"hiking", "reading"}
|
|
```
|
|
|
|
!!! note "Getting a string without quotes"
|
|
|
|
A frequent point of confusion: use [`get`](../api/basic_json/get.md), **not** [`dump`](serialization.md), to read a
|
|
string value. `#!cpp j["name"].get<std::string>()` yields `#!cpp Mary`, whereas `#!cpp j["name"].dump()` yields the
|
|
JSON text `#!cpp "Mary"` (**with** quotes), because `dump` always produces a JSON text.
|
|
|
|
Alternatively, [`get_to`](../api/basic_json/get_to.md) writes into an existing variable and deduces the target type,
|
|
which avoids repeating it:
|
|
|
|
??? example
|
|
|
|
```cpp
|
|
--8<-- "examples/get_to.cpp"
|
|
```
|
|
|
|
Output:
|
|
|
|
```json
|
|
--8<-- "examples/get_to.output"
|
|
```
|
|
|
|
The library already knows how to convert to and from the scalar types and the STL containers (such as
|
|
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
|
|
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
|
|
|
|
```cpp
|
|
json j = {{"one", 1}, {"two", 2}};
|
|
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
|
|
```
|
|
|
|
`#!cpp std::pair` and `#!cpp std::tuple` are also supported, converting positionally to and from a JSON array:
|
|
|
|
```cpp
|
|
json j = {1.0, "hello", 42};
|
|
auto t = j.get<std::tuple<double, std::string, int>>(); // {1.0, "hello", 42}
|
|
```
|
|
|
|
!!! warning "Serializing a `std::pair`/`std::tuple` whose every element is a string-keyed pair"
|
|
|
|
When *every* element of a `#!cpp std::pair` or `#!cpp std::tuple` is itself a two-element array whose first
|
|
element is a string (for example `#!cpp std::pair<std::string, int>`), serializing it produces a JSON **object**
|
|
instead of the expected array:
|
|
|
|
```cpp
|
|
using kv = std::pair<std::string, int>;
|
|
json j = std::pair<kv, kv>{{"a", 1}, {"b", 2}}; // {"a":1,"b":2}, not [["a",1],["b",2]]
|
|
```
|
|
|
|
This is a consequence of the [brace-initializer object-detection rule](creating_values.md): the same rule that
|
|
lets `#!cpp json{{"a", 1}, {"b", 2}}` create an object also fires here. The resulting object cannot be read back
|
|
into the original type (`#!cpp get<std::pair<kv, kv>>()` throws [`type_error.302`](../home/exceptions.md#jsonexceptiontype_error302)),
|
|
and duplicate keys collapse into one, losing elements. This only affects `#!cpp std::pair`/`#!cpp std::tuple`
|
|
themselves; a `#!cpp std::vector<std::pair<std::string, int>>`, or a pair/tuple with at least one element that is
|
|
not a string-keyed pair, serializes to an array as expected. To force an array, build one explicitly from the
|
|
elements with [`array`](../api/basic_json/array.md):
|
|
|
|
```cpp
|
|
std::pair<kv, kv> p{{"a", 1}, {"b", 2}};
|
|
json a = json::array({p.first, p.second}); // [["a",1],["b",2]]
|
|
```
|
|
|
|
!!! info "Extracting references into a tuple"
|
|
|
|
A tuple type may also hold references (e.g. `#!cpp std::tuple<double&, std::string&>`) to avoid copying: `get`
|
|
then returns a tuple of references pointing directly at the elements stored inside the `basic_json` array,
|
|
rather than a tuple of copies:
|
|
|
|
```cpp
|
|
json j = {1.0, "hello"};
|
|
auto refs = j.get<std::tuple<double&, std::string&>>();
|
|
std::get<1>(refs) = "world"; // modifies j[1] in place
|
|
```
|
|
|
|
A referenced element must name the type the library actually *stores* — one of [`boolean_t`](../api/basic_json/boolean_t.md),
|
|
[`number_integer_t`](../api/basic_json/number_integer_t.md), [`number_unsigned_t`](../api/basic_json/number_unsigned_t.md),
|
|
[`number_float_t`](../api/basic_json/number_float_t.md), [`string_t`](../api/basic_json/string_t.md),
|
|
[`binary_t`](../api/basic_json/binary_t.md), [`array_t`](../api/basic_json/array_t.md), or
|
|
[`object_t`](../api/basic_json/object_t.md). There is nothing else to refer to, so a reference to any other type is a
|
|
compile error even when a conversion would exist: `#!cpp std::tuple<int&>` is rejected, because the library stores a
|
|
`#!cpp number_integer_t` (`#!cpp std::int64_t` by default) and not an `#!cpp int`. This restriction applies only to
|
|
reference elements — a plain `#!cpp std::tuple<int>` converts by value as usual.
|
|
|
|
## Implicit conversions
|
|
|
|
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
|
|
|
|
```cpp
|
|
json j = "Hello";
|
|
std::string s = j; // implicit conversion, same as j.get<std::string>()
|
|
```
|
|
|
|
Implicit conversions are convenient but can be surprising (for example, in overload resolution or with `auto`). They can
|
|
be disabled by defining [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) to `#!cpp 0`,
|
|
which forces the explicit `get` form and can catch unintended conversions at compile time.
|
|
|
|
!!! warning "Conversions do not range-check numbers"
|
|
|
|
Just like C++ itself, the `get` family performs numeric conversions without range checks — retrieving a
|
|
floating-point value as an integer truncates it, and narrowing conversions may overflow. See
|
|
[number conversion](types/number_handling.md#number-conversion) for details and how to guard against it.
|
|
|
|
!!! warning "std::optional direct construction from JSON null throws"
|
|
|
|
Constructing or assigning `std::optional<T>` directly from a JSON value does not correctly produce
|
|
`std::nullopt` for a JSON `null`:
|
|
|
|
```cpp
|
|
json j_null;
|
|
std::optional<std::string> opt = j_null; // ❌ throws type_error 302
|
|
```
|
|
|
|
This is due to C++ language rules: `std::optional<T>` has its own converting constructor that is chosen over
|
|
`basic_json::operator T()` when both are viable. Use `get<std::optional<T>>()` or `get_to()` instead:
|
|
|
|
```cpp
|
|
auto opt = j_null.get<std::optional<std::string>>(); // ✅ std::nullopt
|
|
j_null.get_to(opt); // ✅ std::nullopt
|
|
```
|
|
|
|
!!! warning "`static_cast` and `get<std::optional<T>>()` are not guaranteed equivalent"
|
|
|
|
`operator ValueType()` (used by `static_cast` and implicit conversions) intentionally excludes
|
|
`std::optional<T>` from delegating to `get<T>()`, to avoid a constructor ambiguity with
|
|
`std::optional<T>`'s own converting constructor from `basic_json`. As a result,
|
|
`static_cast<std::optional<T>>(json_value)` goes through `std::optional<T>`'s own converting
|
|
constructor rather than through `get<std::optional<T>>()`, which can behave differently -- for example,
|
|
with a custom `adl_serializer<std::optional<T>>` specialization. Prefer `get<std::optional<T>>()`/`get_to()`
|
|
over `static_cast` for optional types.
|
|
|
|
!!! warning "Converting to a fixed-size destination does not check the array size"
|
|
|
|
Some destination types have a size that is fixed by their C++ type rather than by the JSON value:
|
|
`#!cpp std::pair<A, B>`, `#!cpp std::tuple<Ts...>`, `#!cpp std::array<T, N>`, C arrays `#!cpp T[N]`, and
|
|
`#!cpp std::map`/`#!cpp std::unordered_map` with a non-string key type (which is read from an array of
|
|
two-element arrays). All of them read exactly as many elements as they need via
|
|
[`at`](../api/basic_json/at.md) and **never compare the JSON array's size to that number**. The two
|
|
mismatch directions therefore behave differently:
|
|
|
|
- The JSON array has **too many** elements: the surplus is **silently discarded**, and no exception is
|
|
thrown.
|
|
- The JSON array has **too few** elements: `at` throws
|
|
[`out_of_range.401`](../home/exceptions.md#jsonexceptionout_of_range401) for the first missing index --
|
|
an out-of-range error, not a [`type_error`](../home/exceptions.md#type-errors), even though the cause
|
|
is a shape mismatch.
|
|
|
|
```cpp
|
|
json j = {1, 2, 3, 4, 5};
|
|
|
|
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
|
|
auto p = j.get<std::pair<int, int>>(); // (1, 2) -- elements 3, 4, and 5 silently dropped
|
|
|
|
json k = {1};
|
|
auto q = k.get<std::pair<int, int>>(); // ❌ throws out_of_range.401
|
|
```
|
|
|
|
If a size mismatch is an error in your application, check the size yourself before converting.
|
|
|
|
## Omitting a field when serializing `std::optional`
|
|
|
|
By default, `to_json` for `std::optional<T>` writes either the value or `#!json null` -- there is no built-in way
|
|
to make a field disappear from the serialized object entirely when the `std::optional` is `std::nullopt`. Because
|
|
a specialization of `adl_serializer<std::optional<T>>` only controls how the *value* is converted (it cannot
|
|
prevent the containing object's `to_json` from inserting the key in the first place), omission has to be
|
|
implemented in the *containing* type's `to_json`:
|
|
|
|
```cpp
|
|
struct person {
|
|
std::string name;
|
|
std::optional<int> age;
|
|
};
|
|
|
|
void to_json(json& j, const person& p) {
|
|
j = json{{"name", p.name}};
|
|
if (p.age) {
|
|
j["age"] = *p.age; // key is only inserted when the optional has a value
|
|
}
|
|
}
|
|
```
|
|
|
|
## Putting values in
|
|
|
|
The reverse direction works the same way: assigning or constructing a `json` from a C++ value converts it to JSON.
|
|
|
|
```cpp
|
|
std::vector<int> numbers = {1, 2, 3};
|
|
json j = numbers; // [1,2,3]
|
|
```
|
|
|
|
!!! info "Constructing from a C++20 range view"
|
|
|
|
A `json` array can also be constructed directly from a C++20 range view (`std::ranges::view`), such as the result
|
|
of `std::views::filter` or `std::views::transform` -- no intermediate container is needed:
|
|
|
|
```cpp
|
|
std::vector<int> nums{1, 2, 37, 42, 21};
|
|
auto filtered = nums | std::views::filter([](int i) { return i > 10; });
|
|
json j(filtered); // [37,42,21]
|
|
```
|
|
|
|
This requires [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) to be enabled and is unavailable on MinGW due
|
|
to incomplete C++20 ranges support there.
|
|
|
|
## Your own types
|
|
|
|
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
|
|
`to_json`/`from_json` functions (or use one of the convenience macros). This is described in detail on the
|
|
[arbitrary types conversions](arbitrary_types.md) page. Enums can be mapped to strings as described in
|
|
[specializing enum conversion](enum_conversion.md).
|
|
|
|
## See also
|
|
|
|
- [`get`](../api/basic_json/get.md) - get a copy converted to a given type
|
|
- [`get_to`](../api/basic_json/get_to.md) - convert into an existing variable
|
|
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
|
|
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
|
|
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
|