json_view: no save() and load() on big-endian targets

The image format is little-endian only. Instead of throwing
type_error.320 at runtime, save_image(), load_image() and the members
save() and load() do not exist on a big-endian target, so a call fails to
compile, like set() on a read-only document. type_error.320 remains for
saving a discarded document.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-25 20:41:48 +01:00
1 parent 266432ea65
commit f0e049e7ae
10 files changed
+53 -36

No files matched your search

@@ -55,10 +55,7 @@ validate with overload 2 first, if the original vector must survive a failed loa
## Exceptions ## Exceptions
On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
exception [`save()`](save.md#exceptions) throws there, since the image format is little-endian only.
Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
`save()` could have written, or fails the requested `check`: `save()` could have written, or fails the requested `check`:
| message | when | | message | when |
@@ -95,6 +92,9 @@ are checked once, however many nodes refer to it.
## Notes ## Notes
**Little-endian targets only.** On a big-endian target, `load()` does not exist, just like
[`save()`](save.md#notes): the image format is little-endian, so a call fails to compile.
### `image_check` { #image_check } ### `image_check` { #image_check }
```cpp ```cpp
@@ -31,9 +31,6 @@ Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) i
[discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/ [discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/
[`read()`](read.md) with `allow_exceptions == false` left discarded. [`read()`](read.md) with `allow_exceptions == false` left discarded.
On a big-endian target, throws `type_error.320` with a different message instead: the image format is little-endian
only (see [Notes](#notes)).
Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or
the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets
[`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to. [`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to.
@@ -44,9 +41,6 @@ the decoded strings of the image would individually reach 4 GiB -- the same 32-b
[json.exception.type_error.320] cannot save a discarded json_document [json.exception.type_error.320] cannot save a discarded json_document
``` ```
``` ```
[json.exception.type_error.320] json_document images need a little-endian target
```
```
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document [json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
``` ```
@@ -70,9 +64,9 @@ else -- see [`load`'s Exceptions](load.md#exceptions).
the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original
JSON text if you need to read a saved document back with a future library version. JSON text if you need to read a saved document back with a future library version.
**Little-endian only.** The image is written as raw little-endian bytes, with no byte-swapping. `save()` (and **Little-endian targets only.** The image is written as raw little-endian bytes, with no byte-swapping. On a
[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian big-endian target, `save()` and [`load`](load.md) do not exist: a call fails to compile rather than produce bytes a
reader could not interpret correctly. big-endian reader could not interpret correctly.
## Examples ## Examples
+4 -3
View File
@@ -354,9 +354,10 @@ validation entirely, for an image trusted as much as the process's own memory. S
!!! warning "Experimental" !!! warning "Experimental"
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
stable. It is little-endian only, and tied to the library build that wrote it -- use it to cache a document or to stable. It is little-endian only (on big-endian targets, `save()` and `load()` do not exist), and tied to the
hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON library build that wrote it -- use it to cache a document or to hand one to another process running the *same*
text if a saved document needs to be readable by a future library version. build, not as a long-term storage format; keep the original JSON text if a saved document needs to be readable by
a future library version.
The idea of a document you can read without parsing comes from zero-copy formats such as The idea of a document you can read without parsing comes from zero-copy formats such as
[FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check [FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check
-5
View File
@@ -853,17 +853,12 @@ from JSON text.
[`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an [`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an
[image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document. [image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document.
[`save()`](../api/basic_json_document/save.md) and [`load()`](../api/basic_json_document/load.md) also throw this
exception on a big-endian target, since the image format is little-endian only.
!!! failure "Example messages" !!! failure "Example messages"
``` ```
[json.exception.type_error.320] cannot save a discarded json_document [json.exception.type_error.320] cannot save a discarded json_document
``` ```
```
[json.exception.type_error.320] json_document images need a little-endian target
```
!!! note !!! note
+1 -1
View File
@@ -102,7 +102,7 @@ enum class exception_id : int
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image
discarded_value_used = 321, ///< a discarded value is used discarded_value_used = 321, ///< a discarded value is used
// out_of_range // out_of_range
array_index_out_of_range = 401, ///< an array index is out of range array_index_out_of_range = 401, ///< an array index is out of range
+4 -6
View File
@@ -182,12 +182,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
} }
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere)
/// the document as an image /// the document as an image
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
std::string text_tail; std::string text_tail;
@@ -243,6 +241,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
*o = 0; *o = 0;
return image; return image;
} }
#endif
/// the parts of a number token that the checks need /// the parts of a number token that the checks need
struct number_token struct number_token
@@ -663,13 +662,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere)
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
/// the nodes are copied (so that they are aligned, and edits can change them). /// the nodes are copied (so that they are aligned, and edits can change them).
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
throw_invalid_image("too short"); throw_invalid_image("too short");
@@ -738,6 +735,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building) std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building)
d.discarded = false; d.discarded = false;
} }
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
+5
View File
@@ -1182,6 +1182,10 @@ class basic_json_document
/// how load() checks an image (full, bounds, or none) /// how load() checks an image (full, bounds, or none)
using image_check = detail::view::image_check; using image_check = detail::view::image_check;
#if NLOHMANN_VIEW_LITTLE_ENDIAN
// (the image format is little-endian: big-endian targets have no save()
// and load(), so that a call fails to compile instead of always throwing)
/// The document as an image that load() reads without parsing: the node /// The document as an image that load() reads without parsing: the node
/// index, the text, and the decoded strings. An edited document is /// index, the text, and the decoded strings. An edited document is
/// written in its current state (floats that are not finite become null, /// written in its current state (floats that are not finite become null,
@@ -1223,6 +1227,7 @@ class basic_json_document
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
return d; return d;
} }
#endif
/////////// ///////////
// edits // // edits //
+1 -1
View File
@@ -5565,7 +5565,7 @@ enum class exception_id : int
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image
discarded_value_used = 321, ///< a discarded value is used discarded_value_used = 321, ///< a discarded value is used
// out_of_range // out_of_range
array_index_out_of_range = 401, ///< an array index is out of range array_index_out_of_range = 401, ///< an array index is out of range
+9 -6
View File
@@ -5007,12 +5007,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
} }
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere)
/// the document as an image /// the document as an image
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
std::string text_tail; std::string text_tail;
@@ -5068,6 +5066,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
*o = 0; *o = 0;
return image; return image;
} }
#endif
/// the parts of a number token that the checks need /// the parts of a number token that the checks need
struct number_token struct number_token
@@ -5488,13 +5487,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere)
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
/// the nodes are copied (so that they are aligned, and edits can change them). /// the nodes are copied (so that they are aligned, and edits can change them).
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
throw_invalid_image("too short"); throw_invalid_image("too short");
@@ -5563,6 +5560,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building) std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building)
d.discarded = false; d.discarded = false;
} }
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
@@ -8911,6 +8909,10 @@ class basic_json_document
/// how load() checks an image (full, bounds, or none) /// how load() checks an image (full, bounds, or none)
using image_check = detail::view::image_check; using image_check = detail::view::image_check;
#if NLOHMANN_VIEW_LITTLE_ENDIAN
// (the image format is little-endian: big-endian targets have no save()
// and load(), so that a call fails to compile instead of always throwing)
/// The document as an image that load() reads without parsing: the node /// The document as an image that load() reads without parsing: the node
/// index, the text, and the decoded strings. An edited document is /// index, the text, and the decoded strings. An edited document is
/// written in its current state (floats that are not finite become null, /// written in its current state (floats that are not finite become null,
@@ -8952,6 +8954,7 @@ class basic_json_document
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
return d; return d;
} }
#endif
/////////// ///////////
// edits // // edits //
+22 -1
View File
@@ -28,11 +28,25 @@ using nlohmann::detail::view::node;
#include <random> #include <random>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <test_data.hpp> #include <test_data.hpp>
namespace json_view_image_test
{
// whether a document type has save() and load() (only on little-endian targets)
template<typename T, typename = void>
struct has_save : std::false_type {};
template<typename T>
struct has_save<T, decltype(static_cast<void>(std::declval<const T&>().save()))> : std::true_type {};
template<typename T, typename = void>
struct has_load : std::false_type {};
template<typename T>
struct has_load<T, decltype(static_cast<void>(T::load(std::declval<const std::vector<std::uint8_t>&>())))> : std::true_type {};
} // namespace json_view_image_test
#if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
namespace namespace
@@ -171,6 +185,9 @@ std::uint32_t rng()
TEST_CASE("json_view images: round trips") TEST_CASE("json_view images: round trips")
{ {
static_assert(json_view_image_test::has_save<json_document>::value && json_view_image_test::has_load<json_document>::value, "save() and load() on a little-endian target");
static_assert(json_view_image_test::has_save<ordered_json_editable_document>::value && json_view_image_test::has_load<ordered_json_editable_document>::value, "save() and load() on a little-endian target");
SECTION("small documents") SECTION("small documents")
{ {
for (const char* text : for (const char* text :
@@ -1275,8 +1292,12 @@ TEST_CASE("json_view images: damaged images")
TEST_CASE("json_view images: big-endian targets") TEST_CASE("json_view images: big-endian targets")
{ {
// the image format is little-endian: save() and load() do not exist here
static_assert(!json_view_image_test::has_save<json_document>::value, "no save() on a big-endian target");
static_assert(!json_view_image_test::has_load<json_document>::value, "no load() on a big-endian target");
static_assert(!json_view_image_test::has_save<json_editable_document>::value, "no save() on a big-endian target");
const json_document d = json_document::parse("[1]"); const json_document d = json_document::parse("[1]");
CHECK_THROWS_WITH_AS(d.save(), "[json.exception.type_error.320] json_document images need a little-endian target", json::type_error&); CHECK(d.root().size() == 1);
} }
#endif #endif