diff --git a/.gitignore b/.gitignore
index bf797c5..2dff2f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-cmake-build-debug
\ No newline at end of file
+cmake-build-debug
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..8cf8b5e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "googletest"]
+ path = googletest
+ url = https://github.com/google/googletest.git
diff --git a/.idea/SimpleJson.iml b/.idea/SimpleJson.iml
index f08604b..c34214c 100644
--- a/.idea/SimpleJson.iml
+++ b/.idea/SimpleJson.iml
@@ -1,2 +1,8 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deployment.xml b/.idea/deployment.xml
index 8451427..394ff60 100644
--- a/.idea/deployment.xml
+++ b/.idea/deployment.xml
@@ -9,13 +9,6 @@
-
-
-
-
-
-
-
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 79b3c94..76bc34c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7963e86..5fae62d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,14 @@
cmake_minimum_required(VERSION 3.26)
project(SimpleJson)
-
+set(GOOGLETEST_VERSION 1.14.0)
set(CMAKE_CXX_STANDARD 17)
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
+add_subdirectory(./googletest/googletest)
+enable_testing()
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src ./googletest/googletest/include)
+link_directories(${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest/lib)
add_executable(SimpleJson main.cpp
src/json.cpp
- src/json.h)
+ src/json.h
+ src/exception.h)
+
+target_link_libraries(${PROJECT_NAME} gtest)
diff --git a/googletest b/googletest
new file mode 160000
index 0000000..dddb219
--- /dev/null
+++ b/googletest
@@ -0,0 +1 @@
+Subproject commit dddb219c3eb96d7f9200f09b0a381f016e6b4562
diff --git a/main.cpp b/main.cpp
index bc8f460..2c348c8 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,6 +1,44 @@
#include
+#include "gtest/gtest.h"
+#include "src/json.h"
-int main() {
- std::cout << "Hello, World!" << std::endl;
- return 0;
+TEST(TestCase, testJsonNodeSetDoule) {
+ simpleJson::JsonNode node(simpleJson::JSON_TYPE_DOUBLE, "key");
+ node.set_value(1.0);
+ EXPECT_EQ(node.as_int(), 1);
+}
+
+TEST(TestCase, testJsonNodeSetString) {
+ simpleJson::JsonNode node(simpleJson::JSON_TYPE_STRING, "key");
+ node.set_value("1.0");
+ const char* s = node.as_string().c_str();
+ EXPECT_STREQ(s,"1.0");
+}
+
+TEST(TestCase, testJsonNodeSetError) {
+ simpleJson::JsonNode node(simpleJson::JSON_TYPE_INT, "key");
+ EXPECT_THROW(node.set_value("1.0"), std::runtime_error);
+}
+
+TEST(TestCase, testJsonNodeSetKey) {
+ simpleJson::JsonNode node(simpleJson::JSON_TYPE_INT, "key");
+ node.set_key("key1");
+ EXPECT_STREQ(node.get_key().c_str(), "key1");
+}
+
+TEST(TestCase,testJsonNodeSetBool) {
+ simpleJson::JsonNode node(simpleJson::JSON_TYPE_BOOL, "key");
+ node.set_value(true);
+ EXPECT_EQ(node.as_bool(), true);
+}
+
+TEST(TestCase, testParseStr) {
+ std::string json_str = "{\"key\":\"1\"}";
+ simpleJson::JsonParser parser(json_str);
+ EXPECT_EQ(parser.get_root().obj_get("key").as_string(), "1");
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
}
diff --git a/src/exception.h b/src/exception.h
new file mode 100644
index 0000000..fe6f73f
--- /dev/null
+++ b/src/exception.h
@@ -0,0 +1,88 @@
+//
+// Created by jun on 2023/12/30.
+//
+
+#ifndef SIMPLEJSON_EXCEPTION_H
+#define SIMPLEJSON_EXCEPTION_H
+
+#include
+#include
+#include
+#include
+
+namespace simpleJson {
+ const std::string_view SYNTAX_ERROR = "Syntax Error";
+ const std::string_view INVALID_TYPE = "Invalid Type";
+
+ class JsonException : public std::exception {
+ protected:
+ std::string message;
+ public:
+ explicit JsonException(std::string_view str = "JsonException") : message{str} {}
+
+ virtual ~JsonException() = default;
+ };
+
+ class JsonTypeError : public JsonException {
+ protected:
+ std::string function_name;
+ JsonValueTypeEnum expected_type;
+ JsonValueTypeEnum actual_type;
+ public:
+ JsonTypeError(std::string_view str, const char *function_name, JsonValueTypeEnum expected,
+ JsonValueTypeEnum actual) : JsonException{str}, function_name(function_name),
+ expected_type(expected), actual_type(actual) {}
+
+ const char *what() {
+ std::stringstream ss;
+ ss << "[JsonTypeError]" << message << ":" << function_name << " expect " << JSON_TYPE_STR[expected_type]
+ << " but found " << JSON_TYPE_STR[actual_type];
+ return ss.str().c_str();
+ }
+ };
+
+ class JsonParseError : public JsonException {
+ protected:
+ int line, column;
+ public:
+ JsonParseError(int line, int column, std::string_view str) : JsonException{str}, line{line}, column{column} {}
+
+ JsonParseError(std::string_view str) : JsonException{str} {}
+
+ const char *what() {
+ std::stringstream ss;
+ ss << "[JsonParseError]" << message << " at line " << line << " column " << column;
+ return ss.str().c_str();
+ }
+ };
+
+ class JsonInvalidSym : public JsonParseError {
+ protected:
+ char detail;
+ public:
+ JsonInvalidSym(int line, int column, char detail) : JsonParseError{line, column, SYNTAX_ERROR},
+ detail(detail) {}
+
+ const char *what() {
+ std::stringstream ss;
+ ss << "[JsonParseError]" << message << " :invalid symbol " << detail << " at line " << line << " column "
+ << column << " detail " << detail;
+ return ss.str().c_str();
+ }
+
+ };
+
+ class JsonInvalidEscapedSym : public JsonInvalidSym {
+ public:
+ JsonInvalidEscapedSym(int line, int column, char detail) : JsonInvalidSym{line, column, detail} {}
+
+ const char *what() {
+ std::stringstream ss;
+ ss << "[JsonParseError]" << message << " :invalid escaped symbol " << detail << " at line " << line
+ << " column " << column << " detail " << detail;
+ return ss.str().c_str();
+ }
+ };
+}
+
+#endif //SIMPLEJSON_EXCEPTION_H
diff --git a/src/json.cpp b/src/json.cpp
index aeaabdd..e2d14f2 100644
--- a/src/json.cpp
+++ b/src/json.cpp
@@ -3,6 +3,7 @@
//
#include "json.h"
+#include "exception.h"
namespace simpleJson {
@@ -149,7 +150,8 @@ namespace simpleJson {
setValueType(JSON_TYPE_LIST);
}
if (type != JSON_TYPE_LIST) {
- throw std::runtime_error("JsonNode::set_list() error: type is not list");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_LIST, type);
+
}
try {
clear();
@@ -166,7 +168,7 @@ namespace simpleJson {
JsonNode &JsonNode::list_get(int index) {
if (type != JSON_TYPE_LIST) {
- throw std::runtime_error("JsonNode::list_get() error: type is not list");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_LIST, type);
} else if (index < 0 || index >= children_size) {
throw std::runtime_error("JsonNode::list_get() error: index out of range");
} else {
@@ -180,7 +182,7 @@ namespace simpleJson {
void JsonNode::list_insert(int index, simpleJson::JsonNodePtr node) {
if (type != JSON_TYPE_LIST) {
- throw std::runtime_error("JsonNode::list_insert() error: type is not list");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_LIST, type);
} else if (index < 0 || index > children_size) {
throw std::runtime_error("JsonNode::list_insert() error: index out of range");
} else {
@@ -204,7 +206,8 @@ namespace simpleJson {
void JsonNode::list_erase(int index) {
if (type != JSON_TYPE_LIST) {
- throw std::runtime_error("JsonNode::list_erase() error: type is not list");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_LIST, type);
+
} else if (index < 0 || index >= children_size) {
throw std::runtime_error("JsonNode::list_erase() error: index out of range");
} else {
@@ -231,7 +234,7 @@ namespace simpleJson {
bool JsonNode::obj_has_item(const std::string &key) {
if (type != JSON_TYPE_OBJECT) {
- throw std::runtime_error("JsonNode::obj_has_item() error: type is not object");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_OBJECT, type);
} else {
JsonNodePtr p = child_head;
while (p != nullptr) {
@@ -244,9 +247,9 @@ namespace simpleJson {
}
}
- JsonNode& JsonNode::obj_get(const std::string &key) {
+ JsonNode &JsonNode::obj_get(const std::string &key) {
if (type != JSON_TYPE_OBJECT) {
- throw std::runtime_error("JsonNode::obj_get() error: type is not object");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_OBJECT, type);
} else {
JsonNodePtr p = child_head;
while (p != nullptr) {
@@ -255,13 +258,13 @@ namespace simpleJson {
}
p = p->next;
}
- throw std::runtime_error("JsonNode::obj_get() error: key \""+key+"\" not found");
+ throw std::runtime_error("JsonNode::obj_get() error: key \"" + key + "\" not found");
}
}
void JsonNode::obj_set(simpleJson::JsonNodePtr node) {
if (type != JSON_TYPE_OBJECT) {
- throw std::runtime_error("JsonNode::obj_set() error: type is not object");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_OBJECT, type);
} else {
if (obj_has_item(node->key)) {
obj_get(node->key) = *node;
@@ -278,7 +281,7 @@ namespace simpleJson {
if (key.size() != 0 && _print_key) {
_dst_str += "\"" + key + "\":";
}
- int dep = _one_line ? 0 : _tab_dep+1;
+ int dep = _one_line ? 0 : _tab_dep + 1;
switch (type) {
case JSON_TYPE_INT: {
_dst_str += std::to_string(value.int_value);
@@ -346,12 +349,12 @@ namespace simpleJson {
}
- JsonNode& JsonNode::operator[](const std::string & key) {
+ JsonNode &JsonNode::operator[](const std::string &key) {
if (type == JSON_TYPE_NULL) {
setValueType(JSON_TYPE_OBJECT);
}
if (type != JSON_TYPE_OBJECT) {
- throw std::runtime_error("JsonNode::operator[]() error: type is not object");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_OBJECT, type);
} else {
if (obj_has_item(key)) {
return obj_get(key);
@@ -363,12 +366,12 @@ namespace simpleJson {
}
}
- JsonNode& JsonNode::operator[](int index) {
+ JsonNode &JsonNode::operator[](int index) {
if (type == JSON_TYPE_NULL) {
setValueType(JSON_TYPE_LIST);
}
if (type != JSON_TYPE_LIST) {
- throw std::runtime_error("JsonNode::operator[]() error: type is not list");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_LIST, type);
} else {
if (index < 0 || index >= children_size) {
throw std::runtime_error("JsonNode::operator[]() error: index out of range");
@@ -382,7 +385,7 @@ namespace simpleJson {
if (type == JSON_TYPE_NULL) {
setValueType(JSON_TYPE_DOUBLE);
}
- if (type == JSON_TYPE_DOUBLE) {
+ if (type == JSON_TYPE_DOUBLE) {
value.double_value = _value;
return _value;
} else if (type == JSON_TYPE_INT) {
@@ -392,7 +395,7 @@ namespace simpleJson {
value.bool_value = _value > 0;
return _value;
} else {
- throw std::runtime_error("JsonNode::operator=(double) error: type is not number");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_INT, type);
}
}
@@ -411,7 +414,7 @@ namespace simpleJson {
return 0;
}
default: {
- throw std::runtime_error("JsonNode::as_int() error: type is not number");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_INT, type);
}
}
}
@@ -431,7 +434,7 @@ namespace simpleJson {
return 0.0;
}
default: {
- throw std::runtime_error("JsonNode::as_double() error: type is not number");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_DOUBLE, type);
}
}
}
@@ -454,12 +457,12 @@ namespace simpleJson {
return "NULL";
}
default: {
- throw std::runtime_error("JsonNode::as_string() error: type is not string");
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_STRING, type);
}
}
}
- std::vector JsonNode::as_vector() {
+ std::vector JsonNode::as_vector() {
std::vector vec;
JsonNodePtr p = child_head;
while (p != nullptr) {
@@ -470,7 +473,23 @@ namespace simpleJson {
}
void JsonNode::setValueType(simpleJson::JsonValueTypeEnum _type) {
- type = _type;
+ type = _type;
+ }
+
+ bool JsonNode::as_bool() const {
+ switch (type) {
+ case JSON_TYPE_INT:
+ return static_cast(value.int_value);
+ case JSON_TYPE_DOUBLE:
+ return static_cast(value.double_value);
+ case JSON_TYPE_BOOL:
+ return value.bool_value;
+ case JSON_TYPE_NULL:
+ return false;
+ default:
+ throw JsonTypeError(INVALID_TYPE, __func__, JSON_TYPE_BOOL, type);
+ }
+ return false;
}
/*****************************************************************************************************************/
@@ -501,7 +520,7 @@ namespace simpleJson {
}
}
- JsonReader::JsonReader(std::ifstream & _input_file_stream) {
+ JsonReader::JsonReader(std::ifstream &_input_file_stream) {
if (!_input_file_stream) {
throw std::runtime_error("JsonReader::JsonReader() error: input file stream is not valid");
}
@@ -535,7 +554,6 @@ namespace simpleJson {
if (eof) {
throw std::runtime_error("JsonReader::get_char() error: end of file");
} else {
- curr_ptr++;
if (curr_ptr >= src_str_len) {
eof = 1;
return '\0';
@@ -582,7 +600,9 @@ namespace simpleJson {
}
void JsonReader::skip_space() {
- while (curr_ptr < src_str_len && (src_str[curr_ptr] == ' ' || src_str[curr_ptr] == '\t' || src_str[curr_ptr] == '\n' || src_str[curr_ptr] == '\r')) {
+ while (curr_ptr < src_str_len &&
+ (src_str[curr_ptr] == ' ' || src_str[curr_ptr] == '\t' || src_str[curr_ptr] == '\n' ||
+ src_str[curr_ptr] == '\r')) {
get_char();
}
if (curr_ptr >= src_str_len) {
@@ -594,5 +614,202 @@ namespace simpleJson {
return eof;
}
/****************************************************************************************************************/
+/**********************************************JsonParser********************************************************/
+ JsonParser::JsonParser() {
+ root = JsonNodePtr(new JsonNode(JSON_TYPE_OBJECT));
+ }
+
+ JsonParser::JsonParser(const std::string &_src_str) {
+ root = JsonNodePtr(new JsonNode(JSON_TYPE_OBJECT));
+ try {
+ JsonReader reader(_src_str);
+ parseValue(reader, root);
+ is_parse_success = true;
+ } catch (std::exception &e) {
+ std::cerr << e.what() << std::endl;
+ is_parse_success = false;
+ }
+ }
+
+ JsonParser::JsonParser(std::ifstream &_input_file_stream) {
+ root = JsonNodePtr(new JsonNode(JSON_TYPE_OBJECT));
+ try {
+ JsonReader reader(_input_file_stream);
+ parseValue(reader, root);
+ is_parse_success = true;
+ } catch (std::exception &e) {
+ std::cerr << e.what() << std::endl;
+ is_parse_success = false;
+ }
+ }
+
+ JsonNode &JsonParser::get_root() {
+ return *root;
+ }
+
+ bool JsonParser::is_success() const {
+ return is_parse_success;
+ }
+
+ bool JsonParser::fail() const {
+ return !is_parse_success;
+ }
+
+ void JsonParser::parseValue(simpleJson::JsonReader &reader, simpleJson::JsonNodePtr &node) {
+ reader.skip_space();
+
+ char ch = reader.curr_char();
+ if (ch == '{') {
+ parseObject(reader, node);
+ } else if (ch == '\"') {
+ parseString(reader, node);
+ } else if (ch == '[') {
+ parseList(reader, node);
+ } else if (ch == '-' || isdigit(ch)) {
+ parseNumber(reader, node);
+ } else {
+ if (reader.str_ahead(4) == "true") {
+ node->setValueType(JSON_TYPE_BOOL);
+ node->set_value(1);
+ reader.get_str(4);
+ } else if (reader.str_ahead(5) == "false") {
+ node->setValueType(JSON_TYPE_BOOL);
+ node->set_value(0);
+ reader.get_str(5);
+ } else if (reader.str_ahead(4) == "null" || reader.str_ahead(4) == "NULL") {
+ node->setValueType(JSON_TYPE_NULL);
+ reader.get_str(4);
+ } else {
+ throw JsonParseError(reader.get_curr_line(), reader.get_curr_col(), SYNTAX_ERROR);
+ }
+ }
+ }
+
+ void JsonParser::parseObject(simpleJson::JsonReader &reader, simpleJson::JsonNodePtr &node) {
+ node->setValueType(JSON_TYPE_OBJECT);
+ auto ch = reader.curr_char();
+ if (ch != '{') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ } else {
+ reader.get_char();
+ reader.skip_space();
+ ch = reader.curr_char();
+ }
+
+ while (ch != '}') {
+ if (ch != '\"') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ } else {
+ reader.get_char();
+ ch = reader.curr_char();
+ std::string key;
+ while (ch != '\"') {
+ key += reader.get_char();
+ ch = reader.curr_char();
+ }
+ reader.get_char();
+ reader.skip_space();
+ ch = reader.curr_char();
+
+ if (ch != ':') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ } else {
+ reader.get_char();
+ reader.skip_space();
+ ch = reader.curr_char();
+ JsonNodePtr child(new JsonNode(JSON_TYPE_NULL, key));
+ parseValue(reader, child);
+
+ node->push_back(child);
+
+ reader.skip_space();
+ ch = reader.curr_char();
+
+ if (ch == ',') {
+ reader.get_char();
+ reader.skip_space();
+ } else if (ch != '}') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ } else {
+ reader.get_char();
+ return;
+ }
+ }
+ }
+ ch = reader.curr_char();
+ }
+ reader.get_char();
+ }
+
+ void JsonParser::parseList(simpleJson::JsonReader &reader, simpleJson::JsonNodePtr &node) {
+ node->setValueType(JSON_TYPE_LIST);
+ char ch = reader.curr_char();
+ if (ch != '[') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ } else {
+ reader.get_char();
+ reader.skip_space();
+ ch = reader.curr_char();
+ }
+ while (ch != ']') {
+ JsonNodePtr child(new JsonNode(JSON_TYPE_NULL));
+ parseValue(reader, child);
+ node->push_back(child);
+
+ reader.skip_space();
+ ch = reader.curr_char();
+
+ if (ch == ',') {
+ reader.get_char();
+ reader.skip_space();
+ } else if (ch != ']') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ } else {
+ reader.get_char();
+ return;
+ }
+ ch = reader.curr_char();
+ }
+ reader.get_char();
+ }
+
+ void JsonParser::parseString(simpleJson::JsonReader &reader, simpleJson::JsonNodePtr &node) {
+ node->setValueType(JSON_TYPE_STRING);
+ std::string str;
+ char ch = reader.curr_char();
+ if (ch != '\"') {
+ throw JsonInvalidSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ }
+
+ reader.get_char();
+ ch = reader.curr_char();
+
+ while (ch != '\"') {
+ if (ch == '\\') {
+ reader.get_char();
+ ch = reader.curr_char();
+ if (ch == '\"' || ch == '\\' || ch == '/' || ch == '\b' || ch == '\f' || ch == '\n' || ch == '\r' ||
+ ch == '\t') {
+ str += ch;
+ } else if (ch == 'u') {
+ //unicode TODO
+ } else {
+ throw JsonInvalidEscapedSym(reader.get_curr_line(), reader.get_curr_col(), ch);
+ }
+ reader.get_char();
+ ch = reader.curr_char();
+ } else {
+ str += ch;
+ reader.get_char();
+ ch = reader.curr_char();
+ }
+ }
+ reader.get_char();
+ node->set_value(str);
+ }
+
+ void JsonParser::parseNumber(simpleJson::JsonReader &reader, simpleJson::JsonNodePtr &node) {}
+
+/****************************************************************************************************************/
}
\ No newline at end of file
diff --git a/src/json.h b/src/json.h
index 46820f6..70fbe61 100644
--- a/src/json.h
+++ b/src/json.h
@@ -29,6 +29,15 @@ namespace simpleJson {
JSON_TYPE_LIST,
JSON_TYPE_OBJECT
};
+ const static std::string_view JSON_TYPE_STR[] = {
+ "JSON_TYPE_NULL",
+ "JSON_TYPE_INT",
+ "JSON_TYPE_DOUBLE",
+ "JSON_TYPE_BOOL",
+ "JSON_TYPE_STRING",
+ "JSON_TYPE_LIST",
+ "JSON_TYPE_OBJECT"
+ };
union JsonValue {
int int_value;
double double_value;