From 30f7d74c7cafbe6876a8b9868c92b9967beb9f24 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 11 Jul 2025 11:18:24 +0200 Subject: [PATCH] JsonEncoder: wrap writer for flushing This commit introduces intruduces a small helper class that wraps any writer and provides a flush operation that performs the corresponding action if the writer is an AsyncJsonWriter and does nothing otherwise. --- lib/base/json.cpp | 61 ++++++++++++++++++++++++++--------------------- lib/base/json.hpp | 17 ++++++++++--- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 00d114731..117e00e55 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -24,7 +24,7 @@ JsonEncoder::JsonEncoder(std::basic_ostream& stream, bool prettify) } JsonEncoder::JsonEncoder(nlohmann::detail::output_adapter_t w, bool prettify) - : m_IsAsyncWriter{dynamic_cast(w.get()) != nullptr}, m_Pretty(prettify), m_Writer(std::move(w)) + : m_Flusher{w}, m_Pretty(prettify), m_Writer(std::move(w)) { } @@ -112,7 +112,7 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_contex WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; Encode(item, yc); - FlushIfSafe(yc); + m_Flusher.FlushIfSafe(yc); } EndContainer(']', isEmpty); } @@ -134,7 +134,7 @@ void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boo WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; Encode(*result, yc); - FlushIfSafe(yc); + m_Flusher.FlushIfSafe(yc); } EndContainer(']', isEmpty); } @@ -171,7 +171,7 @@ void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& ex Write(m_Pretty ? ": " : ":"); Encode(extractor(val), yc); - FlushIfSafe(yc); + m_Flusher.FlushIfSafe(yc); } EndContainer('}', isEmpty); } @@ -223,29 +223,6 @@ void JsonEncoder::EncodeNumber(double value) const EncodeNlohmannJson(value); } -/** - * Flushes the output stream if it is safe to do so. - * - * Safe flushing means that it only performs the flush operation if the @c JsonEncoder has not acquired - * any object lock so far. This is to ensure that the stream can safely perform asynchronous operations - * without risking undefined behaviour due to coroutines being suspended while the stream is being flushed. - * - * When the @c yc parameter is provided, it indicates that it's safe to perform asynchronous operations, - * and the function will attempt to flush if the writer is an instance of @c AsyncJsonWriter. - * - * @param yc The yield context to use for asynchronous operations. - */ -void JsonEncoder::FlushIfSafe(boost::asio::yield_context* yc) const -{ - if (yc && m_IsAsyncWriter) { - // The m_IsAsyncWriter flag is a constant, and it will never change, so we can safely static - // cast the m_Writer to AsyncJsonWriter without any additional checks as it is guaranteed - // to be an instance of AsyncJsonWriter when m_IsAsyncWriter is true. - auto ajw(static_cast(m_Writer.get())); - ajw->Flush(*yc); - } -} - /** * Writes a string to the underlying output stream. * @@ -313,6 +290,36 @@ void JsonEncoder::WriteSeparatorAndIndentStrIfNeeded(bool emitComma) const } } +/** + * Wraps any writer of type @c nlohmann::detail::output_adapter_t into a Flusher + * + * @param w The writer to wrap. + */ +JsonEncoder::Flusher::Flusher(const nlohmann::detail::output_adapter_t& w) + : m_AsyncWriter(dynamic_cast(w.get())) +{ +} + +/** + * Flushes the underlying writer if it supports that operation and is safe to do so. + * + * Safe flushing means that it only performs the flush operation if the @c JsonEncoder has not acquired + * any object lock so far. This is to ensure that the stream can safely perform asynchronous operations + * without risking undefined behaviour due to coroutines being suspended while the stream is being flushed. + * + * When the @c yc parameter is provided, it indicates that it's safe to perform asynchronous operations, + * and the function will attempt to flush if the writer is an instance of @c AsyncJsonWriter. Otherwise, + * this function does nothing. + * + * @param yc The yield context to use for asynchronous operations. + */ +void JsonEncoder::Flusher::FlushIfSafe(boost::asio::yield_context* yc) const +{ + if (yc && m_AsyncWriter) { + m_AsyncWriter->Flush(*yc); + } +} + class JsonSax : public nlohmann::json_sax { public: diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 238c9bc53..d7e9bdab5 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -83,8 +83,6 @@ private: void EncodeNlohmannJson(const nlohmann::json& json) const; void EncodeNumber(double value) const; - void FlushIfSafe(boost::asio::yield_context* yc) const; - void Write(const std::string_view& sv) const; void BeginContainer(char openChar); void EndContainer(char closeChar, bool isContainerEmpty = false); @@ -93,7 +91,6 @@ private: // The number of spaces to use for indentation in prettified JSON. static constexpr uint8_t m_IndentSize = 4; - const bool m_IsAsyncWriter; // Whether the writer is an instance of AsyncJsonWriter. bool m_Pretty; // Whether to pretty-print the JSON output. unsigned m_Indent{0}; // The current indentation level for pretty-printing. /** @@ -106,6 +103,20 @@ private: // The output stream adapter for writing JSON data. This can be either a std::ostream or an Asio stream adapter. nlohmann::detail::output_adapter_t m_Writer; + + /** + * This class wraps any @c nlohmann::detail::output_adapter_t and provides a method to flush it as required. + * Only @c AsyncJsonWriter supports the flush operation, the class is safe to use with them and the flush method + * does nothing for them. + */ + class Flusher { + public: + explicit Flusher(const nlohmann::detail::output_adapter_t& w); + void FlushIfSafe(boost::asio::yield_context* yc) const; + + private: + AsyncJsonWriter* m_AsyncWriter; + } m_Flusher; }; String JsonEncode(const Value& value, bool prettify = false);