/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/json.hpp" #include "base/debug.hpp" #include "base/namespace.hpp" #include "base/dictionary.hpp" #include "base/array.hpp" #include "base/objectlock.hpp" #include "base/convert.hpp" #include "base/utility.hpp" #include #include #include #include #include #include #include using namespace icinga; class JsonSax : public nlohmann::json_sax { public: bool null() override; bool boolean(bool val) override; bool number_integer(number_integer_t val) override; bool number_unsigned(number_unsigned_t val) override; bool number_float(number_float_t val, const string_t& s) override; bool string(string_t& val) override; bool binary(binary_t& val) override; bool start_object(std::size_t elements) override; bool key(string_t& val) override; bool end_object() override; bool start_array(std::size_t elements) override; bool end_array() override; bool parse_error(std::size_t position, const std::string& last_token, const nlohmann::detail::exception& ex) override; Value GetResult(); private: Value m_Root; std::stack> m_CurrentSubtree; String m_CurrentKey; void FillCurrentTarget(Value value); }; const char l_Null[] = "null"; const char l_False[] = "false"; const char l_True[] = "true"; const char l_Indent[] = " "; // https://github.com/nlohmann/json/issues/1512 template class JsonEncoder { public: void Null(); void Boolean(bool value); void NumberFloat(double value); void Strng(String value); void StartObject(); void Key(String value); void EndObject(); void StartArray(); void EndArray(); String GetResult(); private: std::vector m_Result; String m_CurrentKey; std::stack> m_CurrentSubtree; void AppendChar(char c); template void AppendChars(Iterator begin, Iterator end); void AppendJson(nlohmann::json json); void BeforeItem(); void FinishContainer(char terminator); }; template void Encode(JsonEncoder& stateMachine, const Value& value); template inline void EncodeNamespace(JsonEncoder& stateMachine, const Namespace::Ptr& ns) { stateMachine.StartObject(); ObjectLock olock(ns); for (const Namespace::Pair& kv : ns) { stateMachine.Key(Utility::ValidateUTF8(kv.first)); Encode(stateMachine, kv.second.Val); } stateMachine.EndObject(); } template inline void EncodeDictionary(JsonEncoder& stateMachine, const Dictionary::Ptr& dict) { stateMachine.StartObject(); ObjectLock olock(dict); for (const Dictionary::Pair& kv : dict) { stateMachine.Key(Utility::ValidateUTF8(kv.first)); Encode(stateMachine, kv.second); } stateMachine.EndObject(); } template inline void EncodeArray(JsonEncoder& stateMachine, const Array::Ptr& arr) { stateMachine.StartArray(); ObjectLock olock(arr); for (const Value& value : arr) { Encode(stateMachine, value); } stateMachine.EndArray(); } template void Encode(JsonEncoder& stateMachine, const Value& value) { switch (value.GetType()) { case ValueNumber: stateMachine.NumberFloat(value.Get()); break; case ValueBoolean: stateMachine.Boolean(value.ToBool()); break; case ValueString: stateMachine.Strng(Utility::ValidateUTF8(value.Get())); break; case ValueObject: { const Object::Ptr& obj = value.Get(); { Namespace::Ptr ns = dynamic_pointer_cast(obj); if (ns) { EncodeNamespace(stateMachine, ns); break; } } { Dictionary::Ptr dict = dynamic_pointer_cast(obj); if (dict) { EncodeDictionary(stateMachine, dict); break; } } { Array::Ptr arr = dynamic_pointer_cast(obj); if (arr) { EncodeArray(stateMachine, arr); break; } } // obj is most likely a function => "Object of type 'Function'" Encode(stateMachine, obj->ToString()); break; } case ValueEmpty: stateMachine.Null(); break; default: VERIFY(!"Invalid variant type."); } } String icinga::JsonEncode(const Value& value, bool pretty_print) { if (pretty_print) { JsonEncoder stateMachine; Encode(stateMachine, value); return stateMachine.GetResult() + "\n"; } else { JsonEncoder stateMachine; Encode(stateMachine, value); return stateMachine.GetResult(); } } Value icinga::JsonDecode(const String& data) { String sanitized (Utility::ValidateUTF8(data)); JsonSax stateMachine; nlohmann::json::sax_parse(sanitized.Begin(), sanitized.End(), &stateMachine); return stateMachine.GetResult(); } inline bool JsonSax::null() { FillCurrentTarget(Value()); return true; } inline bool JsonSax::boolean(bool val) { FillCurrentTarget(val); return true; } inline bool JsonSax::number_integer(JsonSax::number_integer_t val) { FillCurrentTarget((double)val); return true; } inline bool JsonSax::number_unsigned(JsonSax::number_unsigned_t val) { FillCurrentTarget((double)val); return true; } inline bool JsonSax::number_float(JsonSax::number_float_t val, const JsonSax::string_t&) { FillCurrentTarget((double)val); return true; } inline bool JsonSax::string(JsonSax::string_t& val) { FillCurrentTarget(String(std::move(val))); return true; } inline bool JsonSax::binary(JsonSax::binary_t& val) { FillCurrentTarget(String(val.begin(), val.end())); return true; } inline bool JsonSax::start_object(std::size_t) { auto object (new Dictionary()); FillCurrentTarget(object); m_CurrentSubtree.push({object, nullptr}); return true; } inline bool JsonSax::key(JsonSax::string_t& val) { m_CurrentKey = String(std::move(val)); return true; } inline bool JsonSax::end_object() { m_CurrentSubtree.pop(); m_CurrentKey = String(); return true; } inline bool JsonSax::start_array(std::size_t) { auto array (new Array()); FillCurrentTarget(array); m_CurrentSubtree.push({nullptr, array}); return true; } inline bool JsonSax::end_array() { m_CurrentSubtree.pop(); return true; } inline bool JsonSax::parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) { throw std::invalid_argument(ex.what()); } inline Value JsonSax::GetResult() { return m_Root; } inline void JsonSax::FillCurrentTarget(Value value) { if (m_CurrentSubtree.empty()) { m_Root = value; } else { auto& node (m_CurrentSubtree.top()); if (node.first) { node.first->Set(m_CurrentKey, value); } else { node.second->Add(value); } } } template inline void JsonEncoder::Null() { BeforeItem(); AppendChars((const char*)l_Null, (const char*)l_Null + 4); } template inline void JsonEncoder::Boolean(bool value) { BeforeItem(); if (value) { AppendChars((const char*)l_True, (const char*)l_True + 4); } else { AppendChars((const char*)l_False, (const char*)l_False + 5); } } template inline void JsonEncoder::NumberFloat(double value) { BeforeItem(); // Make sure 0.0 is serialized as 0, so e.g. Icinga DB can parse it as int. if (value < 0) { long long i = value; if (i == value) { AppendJson(i); } else { AppendJson(value); } } else { unsigned long long i = value; if (i == value) { AppendJson(i); } else { AppendJson(value); } } } template inline void JsonEncoder::Strng(String value) { BeforeItem(); AppendJson(std::move(value)); } template inline void JsonEncoder::StartObject() { BeforeItem(); AppendChar('{'); m_CurrentSubtree.push(2); } template inline void JsonEncoder::Key(String value) { m_CurrentKey = std::move(value); } template inline void JsonEncoder::EndObject() { FinishContainer('}'); } template inline void JsonEncoder::StartArray() { BeforeItem(); AppendChar('['); m_CurrentSubtree.push(0); } template inline void JsonEncoder::EndArray() { FinishContainer(']'); } template inline String JsonEncoder::GetResult() { return String(m_Result.begin(), m_Result.end()); } template inline void JsonEncoder::AppendChar(char c) { m_Result.emplace_back(c); } template template inline void JsonEncoder::AppendChars(Iterator begin, Iterator end) { m_Result.insert(m_Result.end(), begin, end); } template inline void JsonEncoder::AppendJson(nlohmann::json json) { nlohmann::detail::serializer(nlohmann::detail::output_adapter(m_Result), ' ').dump(std::move(json), prettyPrint, true, 0); } template inline void JsonEncoder::BeforeItem() { if (!m_CurrentSubtree.empty()) { auto& node (m_CurrentSubtree.top()); if (node[0]) { AppendChar(','); } else { node[0] = true; } if (prettyPrint) { AppendChar('\n'); for (auto i (m_CurrentSubtree.size()); i; --i) { AppendChars((const char*)l_Indent, (const char*)l_Indent + 4); } } if (node[1]) { AppendJson(std::move(m_CurrentKey)); AppendChar(':'); if (prettyPrint) { AppendChar(' '); } } } } template inline void JsonEncoder::FinishContainer(char terminator) { if (prettyPrint && m_CurrentSubtree.top()[0]) { AppendChar('\n'); for (auto i (m_CurrentSubtree.size() - 1u); i; --i) { AppendChars((const char*)l_Indent, (const char*)l_Indent + 4); } } AppendChar(terminator); m_CurrentSubtree.pop(); }