diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 4f03e692e..74bc9f63a 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -287,14 +287,27 @@ private: void FillCurrentTarget(Value value); }; -String icinga::JsonEncode(const Value& value, bool pretty_print) +String icinga::JsonEncode(const Value& value, bool prettify) { std::string output; - JsonEncoder encoder(output, pretty_print); + JsonEncoder encoder(output, prettify); encoder.Encode(value); return String(std::move(output)); } +/** + * Serializes an Icinga Value into a JSON object and writes it to the given output stream. + * + * @param value The value to be JSON serialized. + * @param os The output stream to write the JSON data to. + * @param prettify Whether to pretty print the serialized JSON. + */ +void icinga::JsonEncode(const Value& value, std::ostream& os, bool prettify) +{ + JsonEncoder encoder(os, prettify); + encoder.Encode(value); +} + Value icinga::JsonDecode(const String& data) { String sanitized (Utility::ValidateUTF8(data)); diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 984a8d1d4..63828316c 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -107,7 +107,8 @@ private: nlohmann::detail::output_adapter_t m_Writer; }; -String JsonEncode(const Value& value, bool pretty_print = false); +String JsonEncode(const Value& value, bool prettify = false); +void JsonEncode(const Value& value, std::ostream& os, bool prettify = false); Value JsonDecode(const String& data); } diff --git a/test/base-json.cpp b/test/base-json.cpp index 02bbebb6d..8df282c89 100644 --- a/test/base-json.cpp +++ b/test/base-json.cpp @@ -4,10 +4,13 @@ #include "base/function.hpp" #include "base/namespace.hpp" #include "base/array.hpp" +#include "base/generator.hpp" #include "base/objectlock.hpp" #include "base/json.hpp" #include #include +#include +#include using namespace icinga; @@ -15,26 +18,51 @@ BOOST_AUTO_TEST_SUITE(base_json) BOOST_AUTO_TEST_CASE(encode) { + auto generate = []() -> std::optional { + static int count = 0; + if (++count == 4) { + count = 0; + return std::nullopt; + } + return Value(count); + }; + Dictionary::Ptr input (new Dictionary({ { "array", new Array({ new Namespace() }) }, { "false", false }, - { "float", -1.25 }, + // Use double max value to test JSON encoding of large numbers and trigger boost numeric_cast exceptions + { "max_double", std::numeric_limits::max() }, + // Test the maximum number that can be exact represented by a double is 2^64-2048. + { "max_int_in_double", std::nextafter(std::pow(2, 64), 0.0) }, + { "float", -1.25f }, + { "float_without_fraction", 23.0f }, { "fx", new Function("", []() {}) }, { "int", -42 }, { "null", Value() }, { "string", "LF\nTAB\tAUml\xC3\xA4Ill\xC3" }, { "true", true }, - { "uint", 23u } + { "uint", 23u }, + { "generator", new ValueGenerator(generate) }, + { "empty_generator", new ValueGenerator([]() -> std::optional { return std::nullopt; }) }, })); String output (R"EOF({ "array": [ {} ], + "empty_generator": [], "false": false, "float": -1.25, + "float_without_fraction": 23, "fx": "Object of type 'Function'", + "generator": [ + 1, + 2, + 3 + ], "int": -42, + "max_double": 1.7976931348623157e+308, + "max_int_in_double": 18446744073709549568, "null": null, "string": "LF\nTAB\tAUml\u00e4Ill\ufffd", "true": true, @@ -42,7 +70,12 @@ BOOST_AUTO_TEST_CASE(encode) } )EOF"); - BOOST_CHECK(JsonEncode(input, true) == output); + auto got(JsonEncode(input, true)); + BOOST_CHECK_EQUAL(output, got); + + std::ostringstream oss; + JsonEncode(input, oss, true); + BOOST_CHECK_EQUAL(output, oss.str()); boost::algorithm::replace_all(output, " ", ""); boost::algorithm::replace_all(output, "Objectoftype'Function'", "Object of type 'Function'");