-
Notifications
You must be signed in to change notification settings - Fork 589
Support JSON serialization into any stream like objects #10414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
e10ebdd
to
edc4112
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially, I was thinking to add another overload that allows to directly write the JSON into an Asio stream,
❤️
but since the json library we use doesn't provide such an implementation we'd have to implement quite a lot of unusual things.
Actually, you'd just need a yc-aware streambuf (for ostream) which writes to a stream of your choice.
So for my understanding: The icinga2/third-party/nlohmann_json/json.hpp Lines 23446 to 23447 in 520aed6
icinga2/third-party/nlohmann_json/json.hpp Lines 17421 to 17467 in 520aed6
Now what does the In other words: doesn't this just add a new copy of the JSON to potentially save a copy in another place?
I don't really see how
We don't want to send them to the network individually. But that sounds exactly like the situation buffered streams are made for. |
Well, I guess you already answered your first question. Yes, it would hold a copy of the given
Hm, then I will add that overload once the other questions are answered regarding how the |
Not entirely. In the current implementation, everything ends up in m_Result which is written by barely three methods: Lines 458 to 476 in 520aed6
(AppendChar could even call AppendChars, making them only two.) Simple enough to overload, I guess. |
Alternatively, since we're shipping the json library in this repo, we can just directly patch it to support our Edit: Sorry, they don't use a patched version of the library but a newer version than that we currently use. |
Well, isn't that just the "or [...] like how it's done previously" part?
I'm not sure what to look for in the code search you've linked. Forking the library (that's basically what you're suggesting) is possible, but sounds like something I'd rather try to avoid. |
I've already updated my previous comment and actually they don't use a patched version of the library but they convert their custom type to the
|
No, it's an implementation detail (to visualize the low effort). |
5354178
to
d088a16
Compare
I think, I've implemented now all the requested points in #10408 and also updated the PR description, including listing two remaining open questions. So, please have a look again! |
std::ostream
std::ostream
and Asio stream
d088a16
to
6afd670
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
6afd670
to
a79b2aa
Compare
2d5fade
to
4c77b12
Compare
I've now adjusted the encoder to incrementally build JSON objects and arrays, which allows for more efficient memory usage and avoids the need to store large intermediate results in memory. I've modified the diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index 17e61f160..75356479f 100644
--- a/lib/remote/httpserverconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -458,7 +458,7 @@ bool ProcessRequest(
return false;
}
- http::async_write(stream, response, yc);
+ //http::async_write(stream, response, yc);
stream.async_flush(yc);
return true;
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index f6f049e4e..685d5d928 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -3,6 +3,7 @@
#include "remote/objectqueryhandler.hpp"
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp
index 17e61f160..75356479f 100644
--- a/lib/remote/httpserverconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -458,7 +458,7 @@ bool ProcessRequest(
return false;
}
- http::async_write(stream, response, yc);
+ //http::async_write(stream, response, yc);
stream.async_flush(yc);
return true;
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index f6f049e4e..685d5d928 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -3,6 +3,7 @@
#include "remote/objectqueryhandler.hpp"
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
+#include "base/json.hpp"
#include "base/serializer.hpp"
#include "base/dependencygraph.hpp"
#include "base/configtype.hpp"
@@ -166,9 +167,6 @@ bool ObjectQueryHandler::HandleRequest(
return true;
}
- ArrayData results;
- results.reserve(objs.size());
-
std::set<String> joinAttrs;
std::set<String> userJoinAttrs;
@@ -194,6 +192,10 @@ bool ObjectQueryHandler::HandleRequest(
std::unordered_map<Type*, std::pair<bool, std::unique_ptr<Expression>>> typePermissions;
std::unordered_map<Object*, bool> objectAccessAllowed;
+ auto adapter(std::make_shared<AsioStreamAdapter<AsioTlsStream>>(stream, yc));
+ JsonEncoder encoder(adapter, params && HttpUtility::GetLastParameter(params, "pretty"));
+
+ bool started = false;
for (ConfigObject::Ptr obj : objs) {
DictionaryData result1{
{ "name", obj->GetName() },
@@ -311,15 +313,18 @@ bool ObjectQueryHandler::HandleRequest(
result1.emplace_back("joins", new Dictionary(std::move(joins)));
- results.push_back(new Dictionary(std::move(result1)));
- }
-
- Dictionary::Ptr result = new Dictionary({
- { "results", new Array(std::move(results)) }
- });
+ if (!started) {
+ started = true;
+ response.result(http::status::ok);
+ response.set(http::field::content_type, "application/json");
+ http::response_serializer<http::string_body> serializer(response);
+ http::async_write_header(stream, serializer, yc);
- response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ encoder.Encode(JsonEncoder::BeginObject, JsonEncoder::MakeField("results", JsonEncoder::BeginArray));
+ }
+ encoder.EncodeItem(new Dictionary(std::move(result1)));
+ }
+ encoder.Encode(JsonEncoder::Flush);
return true;
} |
256fc5c
to
10b47ad
Compare
10b47ad
to
85d2fc7
Compare
b810837
to
c9a2434
Compare
c9a2434
to
25dae46
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional:
25dae46
to
09de342
Compare
std::ostream
and Asio streamIt's just a wrapper around the `JsonEncoder` class to simplify its usage.
Replacing invalid UTF-8 characters beforehand by our selves doesn't make any sense, the serializer can literally perform the same replacement ops with the exact same Unicode replacement character (U+FFFD) on its own. So, why not just use it directly? Instead of wasting memory on a temporary `String` object to always UTF-8 validate every and each value, we just use the serializer to directly to dump the replaced char (if any) into the output writer. No memory waste, no fuss!
09de342
to
a33b9a2
Compare
Sorry! As @julianbrost already found out, we don't need to introduce our own std::string output adapter, since the library already provides one on its own. Besides, apart from introducing that new StringOutputAdapter class, I didn't even make use of it in the JsonEncoder class, so it's now completely removed. |
This PR kinda rewrites the whole JSON serialization process allowing now to directly write the JSON tokens to whatever output stream you want, including std::ostream, std::string, or any other stream that implements the
nlohmann::detail::output_adapter_protocol<>
interface. This allows for more flexibility and performance improvements when dealing with JSON data, especially in our HTTP handlers where we can now just write the JSON directly to the HTTP response body without the need for intermediate buffering and perfectly suitable for chunked transfer encoding. I know that @julianbrost didn't want to perform all the JSON serialization by ourselves, but the cost of not doing so is just even higher, since we would have to transform our Value types intonlohmann::json
objects to be able to use the existing JSON serialization of the library. I was just too naive to think that my previously proposed solution would actually bring any improvement, but in fact it was even worse.There are some open questions left regarding this though:
JsonEncoder
only supports writing to a stream, there's still the previous helper functionJsonEncode()
which wraps the encoder and provides a simple interface if required.Test cases with #10491:
fixes #10408
closes #10400