Add basic JSON operations as header only library
authorSangwan Kwon <sangwan.kwon@samsung.com>
Tue, 12 May 2020 06:21:58 +0000 (15:21 +0900)
committer권상완/Security 2Lab(SR)/Engineer/삼성전자 <sangwan.kwon@samsung.com>
Mon, 18 May 2020 00:52:02 +0000 (09:52 +0900)
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 <sangwan.kwon@samsung.com>
src/vist/CMakeLists.txt
src/vist/json/CMakeLists.txt [new file with mode: 0644]
src/vist/json/json.hpp [new file with mode: 0644]
src/vist/json/object.hpp [new file with mode: 0644]
src/vist/json/tests/json.cpp [new file with mode: 0644]
src/vist/json/value.hpp [new file with mode: 0644]

index ec7a5a1..6bd80e7 100644 (file)
@@ -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 (file)
index 0000000..10781ac
--- /dev/null
@@ -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 (file)
index 0000000..74e3552
--- /dev/null
@@ -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 <vist/json/value.hpp>
+#include <vist/json/object.hpp>
+
+#include <sstream>
+#include <stdexcept>
+#include <string>
+
+namespace vist {
+namespace json {
+
+struct Json {
+       Value& operator[](const std::string& key)
+       {
+               if (!this->root.exist(key))
+                       this->root.pairs[key] = std::make_shared<Value>();
+
+               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>();
+               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<Object>(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 (file)
index 0000000..f6c01c9
--- /dev/null
@@ -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 <vist/json/value.hpp>
+
+#include <string>
+#include <unordered_map>
+
+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<Value>();
+
+               return *(this->pairs[key]);
+       }
+
+       std::unordered_map<std::string, std::shared_ptr<Value>> 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 (file)
index 0000000..e6821dc
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+#include <vist/json/json.hpp>
+#include <vist/logger.hpp>
+
+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<int>(json["int"]), -1);
+
+       EXPECT_EQ(json["int"].serialize(), "-1");
+}
+
+TEST(JsonTests, int_type_mismatch)
+{
+       Json json;
+       json["int"] = 1;
+
+       try {
+               static_cast<std::string>(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<std::string>(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<int>(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<int>(result["int"]), 1);
+       EXPECT_EQ(static_cast<std::string>(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 (file)
index 0000000..244e005
--- /dev/null
@@ -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 <stdexcept>
+#include <string>
+#include <type_traits>
+
+namespace vist {
+namespace json {
+
+struct Int;
+struct String;
+struct Object;
+
+template<class T> 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 <typename Type>
+       Value& operator=(const Type& data)
+       {
+               if constexpr (std::is_same_v<Type, int>)
+                       this->leaf = std::make_shared<Int>(data); 
+               else if constexpr (std::is_same_v<typename std::decay<Type>::type, std::string> ||
+                                                  std::is_same_v<typename std::decay<Type>::type, char*>)
+                       this->leaf = std::make_shared<String>(data); 
+               else
+                       static_assert(dependent_false<Type>::value, "Not supported type.");
+
+               return *this;
+       }
+
+       virtual operator int()
+       {
+               if (auto downcast = std::dynamic_pointer_cast<Int>(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<String>(this->leaf); downcast == nullptr)
+                       throw std::runtime_error("Mismatched type.");
+
+               return (*this->leaf).operator std::string();
+       }
+
+       std::shared_ptr<Value> 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