From 85bf7f747481a148ca2fcbb7a830871411aee3ff Mon Sep 17 00:00:00 2001 From: Sangwan Kwon Date: Tue, 12 May 2020 15:21:58 +0900 Subject: [PATCH] Add basic JSON operations as header only library Usable JSON header-only library. - Applied design pattern: Composite pattern - Component structure: Value - Leaf structure: Int, String (To be added: Bool, Null) - Composite structure: Object (To be added: Array) Usage: // Set json object Json json; json["name"] = "sangwan"; json["age"] = 99; // Get json value std::string name = json["name"]; int age = json["age"]; // Serialize json value std::string serialized = json.serialize(); Signed-off-by: Sangwan Kwon --- src/vist/CMakeLists.txt | 1 + src/vist/json/CMakeLists.txt | 16 +++++ src/vist/json/json.hpp | 102 ++++++++++++++++++++++++++++++ src/vist/json/object.hpp | 70 +++++++++++++++++++++ src/vist/json/tests/json.cpp | 117 +++++++++++++++++++++++++++++++++++ src/vist/json/value.hpp | 111 +++++++++++++++++++++++++++++++++ 6 files changed, 417 insertions(+) create mode 100644 src/vist/json/CMakeLists.txt create mode 100644 src/vist/json/json.hpp create mode 100644 src/vist/json/object.hpp create mode 100644 src/vist/json/tests/json.cpp create mode 100644 src/vist/json/value.hpp diff --git a/src/vist/CMakeLists.txt b/src/vist/CMakeLists.txt index ec7a5a1..6bd80e7 100644 --- a/src/vist/CMakeLists.txt +++ b/src/vist/CMakeLists.txt @@ -47,6 +47,7 @@ ADD_DEFINITIONS("-pedantic-errors") ADD_SUBDIRECTORY(common) ADD_SUBDIRECTORY(database) ADD_SUBDIRECTORY(event) +ADD_SUBDIRECTORY(json) ADD_SUBDIRECTORY(klass) ADD_SUBDIRECTORY(logger) ADD_SUBDIRECTORY(query-builder) diff --git a/src/vist/json/CMakeLists.txt b/src/vist/json/CMakeLists.txt new file mode 100644 index 0000000..10781ac --- /dev/null +++ b/src/vist/json/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +FILE(GLOB JSON_TESTS "tests/*.cpp") +ADD_VIST_TEST(${JSON_TESTS}) diff --git a/src/vist/json/json.hpp b/src/vist/json/json.hpp new file mode 100644 index 0000000..74e3552 --- /dev/null +++ b/src/vist/json/json.hpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the language governing permissions and + * limitations under the License + */ +/* + * Usable JSON header-only library. + * - Applied design pattern: Composite pattern + * - Component structure: Value + * - Leaf structure: Int, String (To be added: Bool, Null) + * - Composite structure: Object (To be added: Array) + */ +/* + * Usage: + * // Set json object + * Json json; + * json["name"] = "sangwan"; + * json["age"] = 99; + * + * // Get json value + * std::string name = json["name"]; + * int age = json["age"]; + * + * // Serialize json value + * std::string serialized = json.serialize(); + */ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace vist { +namespace json { + +struct Json { + Value& operator[](const std::string& key) + { + if (!this->root.exist(key)) + this->root.pairs[key] = std::make_shared(); + + return *(this->root.pairs[key]); + } + + // Return the number of 1-depth's elements. + std::size_t size() const noexcept + { + return this->root.size(); + } + + bool exist(const std::string& key) const noexcept + { + return this->root.exist(key); + } + + std::string serialize() const + { + return this->root.serialize(); + } + + void push(const std::string& key, Json& child) + { + auto object = std::make_shared(); + object->pairs = std::move(child.root.pairs); + this->root.pairs[key] = object; + } + + Json pop(const std::string& key) + { + if (!this->root.exist(key)) + throw std::runtime_error("Not exist key."); + + if (auto downcast = std::dynamic_pointer_cast(this->root.pairs[key]); + downcast == nullptr) + throw std::runtime_error("Mismatched type."); + else { + Json json; + json.root.pairs = std::move(downcast->pairs); + this->root.pairs.erase(key); + return json; + } + } + + Object root; +}; + +} // namespace json +} // namespace vist diff --git a/src/vist/json/object.hpp b/src/vist/json/object.hpp new file mode 100644 index 0000000..f6c01c9 --- /dev/null +++ b/src/vist/json/object.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the language governing permissions and + * limitations under the License + */ + +#pragma once + +#include + +#include +#include + +namespace vist { +namespace json { + +struct Object : public Value { + std::size_t size() const noexcept + { + return this->pairs.size(); + } + + bool exist(const std::string& key) const noexcept + { + return this->pairs.find(key) != this->pairs.end(); + } + + std::string serialize() const override + { + std::stringstream ss; + ss << "{ "; + + std::size_t i = 0; + for (const auto& [key, value] : pairs) { + ss << "\"" << key << "\": "; + ss << value->serialize(); + + if (i++ < pairs.size() - 1) + ss << ","; + + ss << " "; + } + ss << "}"; + + return ss.str(); + } + + Value& operator[](const char* key) + { + if (!this->exist(key)) + this->pairs[key] = std::make_shared(); + + return *(this->pairs[key]); + } + + std::unordered_map> pairs; +}; + +} // namespace json +} // namespace vist diff --git a/src/vist/json/tests/json.cpp b/src/vist/json/tests/json.cpp new file mode 100644 index 0000000..e6821dc --- /dev/null +++ b/src/vist/json/tests/json.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +#include + +#include +#include + +using namespace vist::json; + +TEST(JsonTests, int) +{ + Json json; + json["int"] = 1; + + int value = json["int"]; + EXPECT_EQ(value, 1); + + json["int"] = -1; + value = json["int"]; + EXPECT_EQ(value, -1); + + EXPECT_EQ(static_cast(json["int"]), -1); + + EXPECT_EQ(json["int"].serialize(), "-1"); +} + +TEST(JsonTests, int_type_mismatch) +{ + Json json; + json["int"] = 1; + + try { + static_cast(json["int"]); + EXPECT_TRUE(false); + } catch(...) { + EXPECT_TRUE(true); + } +} + +TEST(JsonTests, string) +{ + Json json; + json["string"] = "initial value"; + + std::string value = json["string"]; + EXPECT_EQ(value, "initial value"); + + json["string"] = "changed value"; + value = static_cast(json["string"]); + EXPECT_EQ(value, "changed value"); + + EXPECT_EQ(json["string"].serialize(), "\"changed value\""); +} + +TEST(JsonTests, string_type_mismatch) +{ + Json json; + json["string"] = "initial value"; + + try { + static_cast(json["string"]); + EXPECT_TRUE(false); + } catch(...) { + EXPECT_TRUE(true); + } +} + +TEST(JsonTests, object) +{ + Json root, child; + child["int"] = 1; + child["string"] = "initial value"; + + root.push("child", child); + EXPECT_EQ(root.size(), 1); + + auto result = root.pop("child"); + EXPECT_EQ(root.size(), 0); + EXPECT_EQ(result.size(), 2); + + EXPECT_EQ(static_cast(result["int"]), 1); + EXPECT_EQ(static_cast(result["string"]), "initial value"); +} + +TEST(JsonTests, serialize) +{ + Json json; + json["int"] = 1; + json["string"] = "root value"; + // expected: { "string": "root value", "int": 1 } + EXPECT_EQ(json.serialize(), "{ \"string\": \"root value\", \"int\": 1 }"); + + Json child; + child["int"] = 2; + child["string"] = "child value"; + json.push("child", child); + + // Json don't keep order for performance. + // expected: { "child": { "string": "child value", "int": 2 }, "int": 1, "string": "root value" } + EXPECT_EQ(json.serialize(), + "{ \"child\": { \"string\": \"child value\", " + "\"int\": 2 }, \"int\": 1, \"string\": \"root value\" }"); +} diff --git a/src/vist/json/value.hpp b/src/vist/json/value.hpp new file mode 100644 index 0000000..244e005 --- /dev/null +++ b/src/vist/json/value.hpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the language governing permissions and + * limitations under the License + */ + +#pragma once + +#include +#include +#include + +namespace vist { +namespace json { + +struct Int; +struct String; +struct Object; + +template struct dependent_false : std::false_type {}; + +struct Value { + virtual std::string serialize() const + { + if (leaf == nullptr) + throw std::runtime_error("Leaf is not set yet."); + + return this->leaf->serialize(); + } + + template + Value& operator=(const Type& data) + { + if constexpr (std::is_same_v) + this->leaf = std::make_shared(data); + else if constexpr (std::is_same_v::type, std::string> || + std::is_same_v::type, char*>) + this->leaf = std::make_shared(data); + else + static_assert(dependent_false::value, "Not supported type."); + + return *this; + } + + virtual operator int() + { + if (auto downcast = std::dynamic_pointer_cast(this->leaf); downcast == nullptr) + throw std::runtime_error("Mismatched type."); + + return (*this->leaf).operator int(); + } + + virtual operator std::string() + { + if (auto downcast = std::dynamic_pointer_cast(this->leaf); downcast == nullptr) + throw std::runtime_error("Mismatched type."); + + return (*this->leaf).operator std::string(); + } + + std::shared_ptr leaf; +}; + +struct Int : public Value { + explicit Int(int data) : data(data) + { + } + + std::string serialize() const override + { + return std::to_string(data); + } + + operator int() override + { + return data; + } + + int data = 0; +}; + +struct String : public Value { + explicit String(const std::string& data) : data(data) + { + } + + std::string serialize() const override + { + return "\"" + data + "\""; + } + + operator std::string() override + { + return data; + } + + std::string data; +}; + +} // namespace json +} // namespace vist -- 2.34.1