diff --git a/include/nlohmann/detail/view/input.hpp b/include/nlohmann/detail/view/input.hpp index 7e1fea09a..d028e81e2 100644 --- a/include/nlohmann/detail/view/input.hpp +++ b/include/nlohmann/detail/view/input.hpp @@ -10,6 +10,9 @@ #include // max #include // size_t +#ifndef JSON_NO_IO + #include // FILE, fread +#endif #include // istream, streambuf #include // basic_string, char_traits, string #include // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference @@ -154,6 +157,37 @@ inline void append_stream(std::string& buf, std::istream& is) is.clear(is.rdstate() | std::ios::eofbit); } +/// a pointer to a C stream (never with JSON_NO_IO, which leaves out ) +template +struct is_c_stream +#ifndef JSON_NO_IO + : std::is_same +#else + : std::false_type +#endif +{}; + +#ifndef JSON_NO_IO +/// append the rest of a C stream: in blocks with fread, not character by +/// character (the library's file adapter reads with fgetc); +/// f is not null +inline void append_file(std::string& buf, std::FILE* f) +{ + for (;;) + { + const std::size_t used = buf.size(); + const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096}); + buf.resize(used + room); + const std::size_t got = std::fread(&buf[used], 1, room, f); + buf.resize(used + got); + if (got < room) + { + break; // end of file, or an error (as fgetc, which returns EOF for both) + } + } +} +#endif + } // namespace view } // namespace detail NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index f0fc58dc9..3980b3ecd 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -1528,7 +1528,8 @@ class basic_json_document // NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11 append_input_impl(buf, std::forward(input), std::integral_constant < int, detail::is_contiguous_byte_container::value ? 0 : std::is_base_of::value ? 1 - : detail::view::is_adapter_input::value ? 2 : 3 > {}); + : detail::view::is_adapter_input::value ? 2 + : detail::view::is_c_stream::value ? 4 : 3 > {}); // NOLINTEND(readability-avoid-nested-conditional-operator) } @@ -1550,6 +1551,19 @@ class basic_json_document detail::view::append_adapter(buf, std::move(input.ia)); } +#ifndef JSON_NO_IO + static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant /*C stream*/) + { + if (file == nullptr) + { + // (as before: the library's adapter decides what a null stream yields) + detail::view::append_adapter(buf, detail::input_adapter(file)); + return; + } + detail::view::append_file(buf, file); + } +#endif + template static void append_input_impl(std::string& buf, T&& input, std::integral_constant /*other*/) { diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 6220270c7..666ba7f84 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -5301,6 +5301,9 @@ NLOHMANN_JSON_NAMESPACE_END #include // max #include // size_t +#ifndef JSON_NO_IO + #include // FILE, fread +#endif #include // istream, streambuf #include // basic_string, char_traits, string #include // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference @@ -5446,6 +5449,37 @@ inline void append_stream(std::string& buf, std::istream& is) is.clear(is.rdstate() | std::ios::eofbit); } +/// a pointer to a C stream (never with JSON_NO_IO, which leaves out ) +template +struct is_c_stream +#ifndef JSON_NO_IO + : std::is_same +#else + : std::false_type +#endif +{}; + +#ifndef JSON_NO_IO +/// append the rest of a C stream: in blocks with fread, not character by +/// character (the library's file adapter reads with fgetc); +/// f is not null +inline void append_file(std::string& buf, std::FILE* f) +{ + for (;;) + { + const std::size_t used = buf.size(); + const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096}); + buf.resize(used + room); + const std::size_t got = std::fread(&buf[used], 1, room, f); + buf.resize(used + got); + if (got < room) + { + break; // end of file, or an error (as fgetc, which returns EOF for both) + } + } +} +#endif + } // namespace view } // namespace detail NLOHMANN_JSON_NAMESPACE_END @@ -8823,7 +8857,8 @@ class basic_json_document // NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11 append_input_impl(buf, std::forward(input), std::integral_constant < int, detail::is_contiguous_byte_container::value ? 0 : std::is_base_of::value ? 1 - : detail::view::is_adapter_input::value ? 2 : 3 > {}); + : detail::view::is_adapter_input::value ? 2 + : detail::view::is_c_stream::value ? 4 : 3 > {}); // NOLINTEND(readability-avoid-nested-conditional-operator) } @@ -8845,6 +8880,19 @@ class basic_json_document detail::view::append_adapter(buf, std::move(input.ia)); } +#ifndef JSON_NO_IO + static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant /*C stream*/) + { + if (file == nullptr) + { + // (as before: the library's adapter decides what a null stream yields) + detail::view::append_adapter(buf, detail::input_adapter(file)); + return; + } + detail::view::append_file(buf, file); + } +#endif + template static void append_input_impl(std::string& buf, T&& input, std::integral_constant /*other*/) { diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index caadee629..4c0983174 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -410,6 +410,31 @@ TEST_CASE("json_view") std::istringstream rvalue_source("[1]"); CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source()); + // a C stream is read in blocks as well (more than one block, and a + // text whose length is a multiple of the block size) + for (const std::size_t extra : + { + std::size_t{0}, std::size_t{1} + }) + { + std::FILE* long_file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory) + if (long_file != nullptr) + { + std::string padded = text; + padded.append((4096 - (padded.size() % 4096)) % 4096 + extra, ' '); + CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size()); + CHECK(std::fseek(long_file, 0, SEEK_SET) == 0); + const json_document from_long_file = json_document::parse(long_file); + CHECK(from_long_file.source() == padded); + CHECK(from_long_file.root().materialize() == json::parse(text)); + CHECK(std::fclose(long_file) == 0); // NOLINT(cppcoreguidelines-owning-memory) + } + } + { + json_document from_null; + CHECK_THROWS_AS(from_null = json_document::parse(static_cast(nullptr)), json::parse_error&); + } + // a stream without a buffer is an empty input std::istream no_buffer(nullptr); CHECK(json_document::parse(no_buffer, false).is_discarded());