diff --git a/docs/mkdocs/docs/api/basic_json_document/load.md b/docs/mkdocs/docs/api/basic_json_document/load.md index 36223f63c..f389c0581 100644 --- a/docs/mkdocs/docs/api/basic_json_document/load.md +++ b/docs/mkdocs/docs/api/basic_json_document/load.md @@ -55,10 +55,7 @@ validate with overload 2 first, if the original vector must survive a failed loa ## Exceptions -On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same -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 +Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one `save()` could have written, or fails the requested `check`: | message | when | @@ -95,6 +92,9 @@ are checked once, however many nodes refer to it. ## 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 } ```cpp diff --git a/docs/mkdocs/docs/api/basic_json_document/save.md b/docs/mkdocs/docs/api/basic_json_document/save.md index c47295048..c87d90f07 100644 --- a/docs/mkdocs/docs/api/basic_json_document/save.md +++ b/docs/mkdocs/docs/api/basic_json_document/save.md @@ -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)/ [`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 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. @@ -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] 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 ``` @@ -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 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 -[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian -reader could not interpret correctly. +**Little-endian targets only.** The image is written as raw little-endian bytes, with no byte-swapping. On a +big-endian target, `save()` and [`load`](load.md) do not exist: a call fails to compile rather than produce bytes a +big-endian reader could not interpret correctly. ## Examples diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 4b5498af5..89e26bdb3 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -354,9 +354,10 @@ validation entirely, for an image trusted as much as the process's own memory. S !!! warning "Experimental" 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 - hand one to another process running the *same* 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. + stable. It is little-endian only (on big-endian targets, `save()` and `load()` do not exist), and tied to the + library build that wrote it -- use it to cache a document or to hand one to another process running the *same* + 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 [FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 00d4058dc..dce2c0e9a 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -853,17 +853,12 @@ from JSON text. [`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. -[`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" ``` [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 diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index cdef3019f..d47ed6f17 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -102,7 +102,7 @@ enum class exception_id : int 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 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 // out_of_range array_index_out_of_range = 401, ///< an array index is out of range diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index c13d269e1..637350949 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -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 inline std::vector 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; std::vector compacted; std::string text_tail; @@ -243,6 +241,7 @@ inline std::vector save_image(const document_data& d) *o = 0; return image; } +#endif /// the parts of a number token that the checks need 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)); } +#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; /// 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) { -#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)) { 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().swap(d.large_objects); // (only needed while building) d.discarded = false; } +#endif } // namespace view } // namespace detail diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 2f7821f38..430cc598e 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -1182,6 +1182,10 @@ class basic_json_document /// how load() checks an image (full, bounds, or none) 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 /// index, the text, and the decoded strings. An edited document is /// 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); return d; } +#endif /////////// // edits // diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a9e40693d..cd96e497a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5565,7 +5565,7 @@ enum class exception_id : int 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 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 // out_of_range array_index_out_of_range = 401, ///< an array index is out of range diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 23b698fbc..0065a9ee6 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -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 inline std::vector 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; std::vector compacted; std::string text_tail; @@ -5068,6 +5066,7 @@ inline std::vector save_image(const document_data& d) *o = 0; return image; } +#endif /// the parts of a number token that the checks need 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)); } +#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; /// 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) { -#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)) { 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().swap(d.large_objects); // (only needed while building) d.discarded = false; } +#endif } // namespace view } // namespace detail @@ -8911,6 +8909,10 @@ class basic_json_document /// how load() checks an image (full, bounds, or none) 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 /// index, the text, and the decoded strings. An edited document is /// 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); return d; } +#endif /////////// // edits // diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index e2a738a4a..ff1713371 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -28,11 +28,25 @@ using nlohmann::detail::view::node; #include #include #include +#include #include #include #include +namespace json_view_image_test +{ +// whether a document type has save() and load() (only on little-endian targets) +template +struct has_save : std::false_type {}; +template +struct has_save(std::declval().save()))> : std::true_type {}; +template +struct has_load : std::false_type {}; +template +struct has_load(T::load(std::declval&>())))> : std::true_type {}; +} // namespace json_view_image_test + #if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) namespace @@ -171,6 +185,9 @@ std::uint32_t rng() TEST_CASE("json_view images: round trips") { + static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + SECTION("small documents") { for (const char* text : @@ -1275,8 +1292,12 @@ TEST_CASE("json_view images: damaged images") 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::value, "no save() on a big-endian target"); + static_assert(!json_view_image_test::has_load::value, "no load() on a big-endian target"); + static_assert(!json_view_image_test::has_save::value, "no save() on a big-endian target"); 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