From cd80c3b2a0a41f7b10e2fb617d59fc8862f4dddc Mon Sep 17 00:00:00 2001 From: Junior Date: Wed, 3 Jan 2024 17:18:05 +0800 Subject: [PATCH] add exception, gtest and some parse function --- .gitignore | 2 +- .gitmodules | 3 + .idea/SimpleJson.iml | 8 +- .idea/deployment.xml | 7 -- .idea/misc.xml | 3 + CMakeLists.txt | 12 +- googletest | 1 + main.cpp | 44 ++++++- src/exception.h | 88 ++++++++++++++ src/json.cpp | 265 +++++++++++++++++++++++++++++++++++++++---- src/json.h | 9 ++ 11 files changed, 403 insertions(+), 39 deletions(-) create mode 100644 .gitmodules create mode 160000 googletest create mode 100644 src/exception.h 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;