diff options
Diffstat (limited to 'lib/base/json.cpp')
-rw-r--r-- | lib/base/json.cpp | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/lib/base/json.cpp b/lib/base/json.cpp new file mode 100644 index 0000000..5689330 --- /dev/null +++ b/lib/base/json.cpp @@ -0,0 +1,525 @@ +/* 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 <bitset> +#include <boost/exception_ptr.hpp> +#include <cstdint> +#include <json.hpp> +#include <stack> +#include <utility> +#include <vector> + +using namespace icinga; + +class JsonSax : public nlohmann::json_sax<nlohmann::json> +{ +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<std::pair<Dictionary*, Array*>> 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<bool prettyPrint> +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<char> m_Result; + String m_CurrentKey; + std::stack<std::bitset<2>> m_CurrentSubtree; + + void AppendChar(char c); + + template<class Iterator> + void AppendChars(Iterator begin, Iterator end); + + void AppendJson(nlohmann::json json); + + void BeforeItem(); + + void FinishContainer(char terminator); +}; + +template<bool prettyPrint> +void Encode(JsonEncoder<prettyPrint>& stateMachine, const Value& value); + +template<bool prettyPrint> +inline +void EncodeNamespace(JsonEncoder<prettyPrint>& 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<bool prettyPrint> +inline +void EncodeDictionary(JsonEncoder<prettyPrint>& 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<bool prettyPrint> +inline +void EncodeArray(JsonEncoder<prettyPrint>& stateMachine, const Array::Ptr& arr) +{ + stateMachine.StartArray(); + + ObjectLock olock(arr); + for (const Value& value : arr) { + Encode(stateMachine, value); + } + + stateMachine.EndArray(); +} + +template<bool prettyPrint> +void Encode(JsonEncoder<prettyPrint>& stateMachine, const Value& value) +{ + switch (value.GetType()) { + case ValueNumber: + stateMachine.NumberFloat(value.Get<double>()); + break; + + case ValueBoolean: + stateMachine.Boolean(value.ToBool()); + break; + + case ValueString: + stateMachine.Strng(Utility::ValidateUTF8(value.Get<String>())); + break; + + case ValueObject: + { + const Object::Ptr& obj = value.Get<Object::Ptr>(); + + { + Namespace::Ptr ns = dynamic_pointer_cast<Namespace>(obj); + if (ns) { + EncodeNamespace(stateMachine, ns); + break; + } + } + + { + Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(obj); + if (dict) { + EncodeDictionary(stateMachine, dict); + break; + } + } + + { + Array::Ptr arr = dynamic_pointer_cast<Array>(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<true> stateMachine; + + Encode(stateMachine, value); + + return stateMachine.GetResult() + "\n"; + } else { + JsonEncoder<false> 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<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Null() +{ + BeforeItem(); + AppendChars((const char*)l_Null, (const char*)l_Null + 4); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::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<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::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<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Strng(String value) +{ + BeforeItem(); + AppendJson(std::move(value)); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::StartObject() +{ + BeforeItem(); + AppendChar('{'); + + m_CurrentSubtree.push(2); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::Key(String value) +{ + m_CurrentKey = std::move(value); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::EndObject() +{ + FinishContainer('}'); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::StartArray() +{ + BeforeItem(); + AppendChar('['); + + m_CurrentSubtree.push(0); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::EndArray() +{ + FinishContainer(']'); +} + +template<bool prettyPrint> +inline +String JsonEncoder<prettyPrint>::GetResult() +{ + return String(m_Result.begin(), m_Result.end()); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::AppendChar(char c) +{ + m_Result.emplace_back(c); +} + +template<bool prettyPrint> +template<class Iterator> +inline +void JsonEncoder<prettyPrint>::AppendChars(Iterator begin, Iterator end) +{ + m_Result.insert(m_Result.end(), begin, end); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::AppendJson(nlohmann::json json) +{ + nlohmann::detail::serializer<nlohmann::json>(nlohmann::detail::output_adapter<char>(m_Result), ' ').dump(std::move(json), prettyPrint, true, 0); +} + +template<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::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<bool prettyPrint> +inline +void JsonEncoder<prettyPrint>::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(); +} |