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.
This commit is contained in:
Julian Brost 2025-07-11 11:18:24 +02:00
parent 89418f38ee
commit 30f7d74c7c
2 changed files with 48 additions and 30 deletions

View File

@ -24,7 +24,7 @@ JsonEncoder::JsonEncoder(std::basic_ostream<char>& stream, bool prettify)
}
JsonEncoder::JsonEncoder(nlohmann::detail::output_adapter_t<char> w, bool prettify)
: m_IsAsyncWriter{dynamic_cast<AsyncJsonWriter*>(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<AsyncJsonWriter*>(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<char> into a Flusher
*
* @param w The writer to wrap.
*/
JsonEncoder::Flusher::Flusher(const nlohmann::detail::output_adapter_t<char>& w)
: m_AsyncWriter(dynamic_cast<AsyncJsonWriter*>(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<nlohmann::json>
{
public:

View File

@ -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<char> m_Writer;
/**
* This class wraps any @c nlohmann::detail::output_adapter_t<char> 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<char>& w);
void FlushIfSafe(boost::asio::yield_context* yc) const;
private:
AsyncJsonWriter* m_AsyncWriter;
} m_Flusher;
};
String JsonEncode(const Value& value, bool prettify = false);