#include "JSON.h" #include #include #include #include #define HANDLE_ERROR(reason) \ JSON::handle_error(__func__, reason, __FILE__, __LINE__); namespace Psc { std::function JSON::handle_error = [](const char *function_name, const std::string& reason, const char *file, int line) { std::ostringstream oss; oss << "JSON::" << function_name << " 原因:" << reason << std::endl; oss << "位置:" << file << ":" << line << std::endl; std::cout << oss.str(); Psc::fail_fast(); }; void skip_whitespace(std::string& s, size_t& i); bool parse_string(std::string& s, size_t& i, std::string& out, std::error_code& ec) noexcept; bool parse_number(std::string& s, size_t& i, std::string& out, std::error_code& ec) noexcept; bool parse_object(std::string& s, size_t& i, JSON& out, std::error_code& ec) noexcept; bool parse_array(std::string& s, size_t& i, JSON& out, std::error_code& ec) noexcept; std::ostream& operator<<(std::ostream& os, const JSON& obj) { os << const_cast(obj).to_json_string(); return os; } JSON::operator std::string() const { return const_cast(this)->to_json_string(); } PSC_DEFINE_TRIPLE_API_CONST_METHOD_FROM_BOOL(std::string, JSON, get_string, PSC_JSON_KEY_PARAMS) bool JSON::get_string_ec(const std::string& key, std::string& out, std::error_code& ec) const noexcept { const JSON *that = get(key); if (!that) { ec = std::make_error_code(std::errc::invalid_argument); return false; } out = that->val; return true; } PSC_DEFINE_TRIPLE_API_CONST_METHOD_FROM_BOOL_0(bool, JSON, bool_val) bool JSON::bool_val_ec(bool& out, std::error_code& ec) const noexcept { if (val != "true" && val != "false") { // HANDLE_ERROR(key + " Invalid bool argument: " + val) ec = std::make_error_code(std::errc::invalid_argument); return false; } out = (val == "true"); return true; } PSC_DEFINE_TRIPLE_API_CONST_METHOD_FROM_BOOL(bool, JSON, get_bool, PSC_JSON_KEY_PARAMS) bool JSON::get_bool_ec(const std::string& key, bool& out, std::error_code& ec) const noexcept { const Psc::JSON *that = get(key); if (!that) { ec = std::make_error_code(std::errc::invalid_argument); return false; } return that->bool_val_ec(out, ec); } JSON::JSON(std::string key, const JSON& children) : key(std::move(key)), val(children.val), valueType(children.valueType), children(children.children) {} void JSON::append_list(const std::vector& arr) { #ifdef _DEBUG if (valueType != JsonType::Object && valueType != JsonType::Array) { HANDLE_ERROR("append_list error! key:" + key + " not array or object " + to_json_string()) } #else #endif for (const JSON& j : arr) { children.push_back(j); } } void JSON::append(const JSON& json) { #ifdef _DEBUG if (valueType != JsonType::Object && valueType != JsonType::Array) { HANDLE_ERROR("append_list error! key:" + key + " not array or object " + to_json_string()) } #else #endif children.push_back(json); } void JSON::prepend(const JSON& json) { #ifdef _DEBUG if (valueType != JsonType::Object && valueType != JsonType::Array) { HANDLE_ERROR("append_list error! key:" + key + " not array or object " + to_json_string()) } #else #endif children.insert(children.begin(), json); } bool JSON::has(const std::string& _key) const { for (const JSON& child : children) { if (child.key == _key) { return true; } } return false; } const JSON* JSON::get(const std::string& _key) const { for (const JSON& child : children) { if (child.key == _key) { return &child; } } // HANDLE_ERROR(_key + " not found!" + to_json_string()) return nullptr; } JSON JSON::array(const std::vector& children) { return array("", children); } JSON JSON::object(const std::vector& children) { return object("", children); } JSON JSON::array(std::string key, const std::vector& children) { JSON ret; ret.key = std::move(key); ret.valueType = Array; ret.children = children; return ret; } JSON JSON::object(std::string key, const std::vector& children) { JSON ret; ret.key = std::move(key); ret.valueType = Object; ret.children = children; return ret; } std::string JSON::get_indent(const int& indent, const std::string& indentStr) { std::string ret; for (int i = 0; i < indent; ++i) { ret.append(indentStr); } return ret; } void JSON::append_value_string(std::string& ret, const int& indent, bool isEnd, const std::string& object_split, const std::string& array_split, const std::string& indentStr, JsonType fatherType) const { ret.append(get_indent(indent, indentStr)); if (fatherType == Object) ret.append("\"" + key + "\"" + ": "); const std::string split = fatherType == Array ? array_split : object_split; if (valueType == String) { ret.append("\"" + val + "\""); goto end; } if (valueType == Null) { ret.append("null"); goto end; } if (valueType == Number || valueType == Bool) { ret.append(val); goto end; } if (valueType == Object) { ret.append("{" + object_split); size_t n = children.size(); if (n) { for (size_t i = 0; i < n - 1; ++i) { children[i].append_value_string(ret, indent + 2, false, object_split, array_split, indentStr, Object); } children[n - 1].append_value_string(ret, indent + 2, true, object_split, array_split, indentStr, Object); } ret.append(get_indent(indent, indentStr) + "}"); goto end; } if (valueType == Array) { ret.append("[" + array_split); size_t n = children.size(); if (n) { for (size_t i = 0; i < n - 1; ++i) { children[i].append_value_string(ret, indent + 1, false, object_split, array_split, indentStr, Array); } children[n - 1].append_value_string(ret, indent + 1, true, object_split, array_split, indentStr, Array); } ret.append(get_indent(indent, indentStr) + "]"); goto end; } end: if (!isEnd) ret.append(","); ret.append(split); } void handleBlank(std::string& s) { int n = static_cast(s.length()); bool isInStr = false; for (int i = n - 1; i >= 0; --i) { const char& c = s[i]; if (c == '\"') isInStr = !isInStr; if (isInStr) continue; if (c == '\n' || c == '\t' || c == ' ') { s.erase(i, 1); } } } std::string JSON::to_json_string(const std::string& object_split, const std::string& array_split, const std::string& indentStr, const int& indent) const { if (valueType == Bool) return val; if (valueType == String) return '\"' + val + '\"'; if (valueType == Number) return val; if (valueType == Null) { if (children.size() != 0) { HANDLE_ERROR("null 带有children 转成了字符串 (" + key + "," + val + ")" + to_json_string()) } return "null"; } std::string ret; append_value_string(ret, indent, true, object_split, array_split, indentStr, Null); return ret; } JSON::JSON() = default; void copy(JSON& dest, const JSON& src) { dest.key = src.key; dest.valueType = src.valueType; dest.val = src.val; auto len = src.children.size(); dest.children.resize(len); for (size_t i = 0; i < len; ++i) { dest.children[i] = src.children[i]; } } JSON::JSON(const JSON& other) { copy(*this, other); } JSON::JSON(JSON&& other) noexcept { key = std::move(other.key); valueType = std::move(other.valueType); children = std::move(other.children); val = std::move(other.val); } JSON& JSON::operator=(const JSON& other) { if (this != &other) { // 防止自赋值 copy(*this, other); // 调用拷贝函数 } return *this; } JSON& JSON::operator=(JSON&& other) noexcept { if (this != &other) { key = std::move(other.key); valueType = other.valueType; children = std::move(other.children); val = std::move(other.val); } return *this; } JSON& JSON::operator+=(const JSON& child) { append_list(child.children); return *this; } JSON& JSON::operator+=(JSON&& child) { append_list(child.children); return *this; } // 解析的分割线---------------------------- // 如果未找到非空白字符,将 i 更新为 std::string::npos void skip_whitespace(std::string& s, size_t& i) { i = s.find_first_not_of(" \t\n\r\f\v", i); } // 传入的 i 是 "所在的index bool parse_string(std::string& s, size_t& i, std::string& out, std::error_code& ec) noexcept { ec.clear(); i++; // skip '"' if (s[i] == '"') { i++; out.clear(); return true; } std::string ret; while (true) { if (s[i] == '"' && s[i - 1] != '\\') { break; } ret += s[i]; i++; } i++; out = std::move(ret); return true; } bool parse_number(std::string& s, size_t& i, std::string& out, std::error_code& ec) noexcept { ec.clear(); std::string num; while (isdigit(s[i])) { num += s[i++]; } if (s[i] == '.') { i++; num += '.'; while (isdigit(s[i])) { num += s[i++]; } } out = std::move(num); return true; } bool parse_json_EX(std::string& s, size_t& i, JSON& out, std::error_code& ec) noexcept; PSC_DEFINE_TRIPLE_API_FROM_BOOL(JSON, parse_json, PSC_JSON_PARSE_PARAMS) bool parse_json_ec(const std::string& text, JSON& out, std::error_code& ec) noexcept { auto& t = const_cast(text); size_t i = 0; return parse_json_EX(t, i, out, ec); } bool parse_json_EX(std::string& s, size_t& i, JSON& out, std::error_code& ec) noexcept { skip_whitespace(s, i); switch (s[i]) { case 'n': i += 4; out = JSON(nullptr); return true; case 't': i += 4; out = JSON(true); return true; case 'f': i += 5; out = JSON(false); return true; case '{': return parse_object(s, i, out, ec); case '[': return parse_array(s, i, out, ec); case '"': { std::string str; if (!parse_string(s, i, str, ec)) return false; out = JSON(std::move(str)); return true; } case '-': { i++; // skip - JSON ret(nullptr); ret.valueType = Number; std::string num; if (!parse_number(s, i, num, ec)) return false; ret.val = '-' + num; out = std::move(ret); return true; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { JSON ret(nullptr); ret.valueType = Number; std::string num; if (!parse_number(s, i, num, ec)) return false; ret.val = std::move(num); out = std::move(ret); return true; } default: { // HANDLE_ERROR("json 解析错误! " + s + "\n") ec = std::make_error_code(std::errc::invalid_argument); return false; } } } bool parse_object(std::string& s, size_t& i, JSON& out, std::error_code& ec) noexcept { i++; // skip '{' skip_whitespace(s, i); JSON ret = JSON::object(); if (s[i] == '}') { i++; out = std::move(ret); return true; } while (true) { skip_whitespace(s, i); std::string key; if (!parse_string(s, i, key, ec)) return false; skip_whitespace(s, i); if (s[i] != ':') { ec = std::make_error_code(std::errc::invalid_argument); return false; } i++; skip_whitespace(s, i); JSON kv; if (!parse_json_EX(s, i, kv, ec)) return false; skip_whitespace(s, i); kv.key = key; ret.children.push_back(std::move(kv)); if (s[i] == '}') { i++; break; } if (s[i] == ',') { i++; } } out = std::move(ret); return true; } bool parse_array(std::string& s, size_t& i, JSON& out, std::error_code& ec) noexcept { i++; // skip '[' skip_whitespace(s, i); JSON ret = JSON::array(); if (s[i] == ']') { i++; out = std::move(ret); return true; } while (true) { skip_whitespace(s, i); // parseJson(s, i) JSON kv; if (!parse_json_EX(s, i, kv, ec)) return false; ret.children.push_back(std::move(kv)); skip_whitespace(s, i); if (s[i] == ']') { i++; break; } if (s[i] == ',') { i++; } } out = std::move(ret); return true; } PSC_DEFINE_TRIPLE_API_FROM_BOOL(JSON, parse_json_file, PSC_JSON_FILE_PARAMS) bool parse_json_file_ec(const std::string& file_name, JSON& out, std::error_code& ec) noexcept { auto readFile = [](const std::string& path, std::string& content, std::error_code& ec) noexcept { std::ifstream infile(path, std::ios::binary | std::ios::ate); if (!infile.is_open()) { ec = std::make_error_code(std::errc::no_such_file_or_directory); return false; } std::streamsize fileSize = infile.tellg(); if (fileSize < 0) { ec = std::make_error_code(std::errc::io_error); return false; } infile.seekg(0, std::ios::beg); content.resize(static_cast(fileSize)); if (!infile.read(content.data(), fileSize)) { ec = std::make_error_code(std::errc::io_error); return false; } return true; }; std::string text; if (!readFile(file_name, text, ec)) return false; return parse_json_ec(text, out, ec); } void check_json_assign_field(bool ok, const std::error_code& ec, const Psc::JSON *that_json, const char *name) { if (!ok) { std::ostringstream oss; oss << "json assign error:[" << name << "," << Psc::platform_2_utf8(ec.message()) << "]"; if (that_json != nullptr) { oss << " from" << that_json->to_json_string(); } oss << std::endl; throw json_assign_error(ec, oss.str()); } } } // namespace Psc #undef HANDLE_ERROR #ifdef _USE_GTEST #include #include #include #include #include #include #include namespace { std::string compact_json(const Psc::JSON& json) { return json.to_json_string("", "", "", 0); } struct TempFileGuard { const char *path; ~TempFileGuard() { std::remove(path); } }; struct JsonCase { const char *name; const char *text; }; const std::vector& json_cases() { static const std::vector cases = { { "object_basic", R"({ "name": "psc", "debug": true, "version": 1, "empty": null })" }, { "nested_object", R"({ "project": { "name": "psc", "enabled": true, "config": { "host": "127.0.0.1", "port": 8080, "ssl": false } }, "status": "ok" })" }, { "array_with_objects", R"({ "users": [ {"name": "tom", "admin": true}, {"name": "jerry", "admin": false}, {"name": "alice", "admin": true} ], "count": 3 })" }, { "deep_mixed", R"({ "app": { "name": "server", "modules": [ { "name": "http", "enabled": true, "routes": [ {"path": "/", "method": "GET"}, {"path": "/api", "method": "POST"} ] }, { "name": "db", "enabled": false, "config": { "host": "localhost", "port": 5432, "pool": { "min": 1, "max": 8 } } } ] }, "ok": true, "none": null })" }, { "root_array", R"([ { "name": "one", "value": 1, "ok": true }, { "name": "two", "value": 2, "ok": false, "children": [ {"name": "child1"}, {"name": "child2"} ] }, null, true, false, 123, "text" ])" } }; return cases; } void assert_parse_ok(const char *text, Psc::JSON& out) { std::error_code ec; ASSERT_TRUE(Psc::parse_json_ec(text, out, ec)) << ec.message(); ASSERT_FALSE(ec) << ec.message(); } void assert_round_trip(const char *text) { Psc::JSON first; assert_parse_ok(text, first); const std::string first_text = compact_json(first); Psc::JSON second; assert_parse_ok(first_text.c_str(), second); const std::string second_text = compact_json(second); EXPECT_EQ(second_text, first_text); } } // namespace TEST(JSONTest, ParseAndRoundTripComplexJsonList) { for (const JsonCase& item : json_cases()) { SCOPED_TRACE(item.name); assert_round_trip(item.text); } } TEST(JSONTest, ParseObjectAndGetFields) { Psc::JSON json; assert_parse_ok( R"({ "name": "psc", "debug": true, "version": 1, "empty": null })", json); EXPECT_TRUE(json.has("name")); EXPECT_TRUE(json.has("debug")); EXPECT_TRUE(json.has("version")); EXPECT_TRUE(json.has("empty")); EXPECT_FALSE(json.has("missing")); std::error_code ec; std::string name; ASSERT_TRUE(json.get_string_ec("name", name, ec)); EXPECT_EQ(name, "psc"); bool debug = false; ASSERT_TRUE(json.get_bool_ec("debug", debug, ec)); EXPECT_TRUE(debug); } TEST(JSONTest, ParseNestedObjectAndGetTopFields) { Psc::JSON json; assert_parse_ok( R"({ "project": { "name": "psc", "enabled": true, "config": { "host": "127.0.0.1", "port": 8080 } }, "status": "ok" })", json); EXPECT_TRUE(json.has("project")); EXPECT_TRUE(json.has("status")); std::error_code ec; std::string status; ASSERT_TRUE(json.get_string_ec("status", status, ec)); EXPECT_EQ(status, "ok"); const Psc::JSON *project = json.get("project"); ASSERT_NE(project, nullptr); EXPECT_TRUE(project->has("name")); EXPECT_TRUE(project->has("enabled")); EXPECT_TRUE(project->has("config")); std::string project_name; ASSERT_TRUE(project->get_string_ec("name", project_name, ec)); EXPECT_EQ(project_name, "psc"); bool enabled = false; ASSERT_TRUE(project->get_bool_ec("enabled", enabled, ec)); EXPECT_TRUE(enabled); } TEST(JSONTest, CopyConstructorKeepsComplexJson) { for (const JsonCase& item : json_cases()) { SCOPED_TRACE(item.name); Psc::JSON original; assert_parse_ok(item.text, original); const std::string expected = compact_json(original); Psc::JSON copied(original); EXPECT_EQ(compact_json(copied), expected); original = Psc::JSON{}; assert_parse_ok(R"({"changed":true})", original); EXPECT_EQ(compact_json(copied), expected); } } TEST(JSONTest, CopyAssignmentKeepsComplexJson) { for (const JsonCase& item : json_cases()) { SCOPED_TRACE(item.name); Psc::JSON source; Psc::JSON target; assert_parse_ok(item.text, source); assert_parse_ok(R"({"old":false})", target); const std::string expected = compact_json(source); target = source; EXPECT_EQ(compact_json(target), expected); source = Psc::JSON{}; assert_parse_ok(R"({"changed":true})", source); EXPECT_EQ(compact_json(target), expected); } } TEST(JSONTest, MoveConstructorKeepsComplexJson) { for (const JsonCase& item : json_cases()) { SCOPED_TRACE(item.name); Psc::JSON source; assert_parse_ok(item.text, source); const std::string expected = compact_json(source); Psc::JSON moved(std::move(source)); EXPECT_EQ(compact_json(moved), expected); } } TEST(JSONTest, MoveAssignmentKeepsComplexJson) { for (const JsonCase& item : json_cases()) { SCOPED_TRACE(item.name); Psc::JSON source; Psc::JSON target; assert_parse_ok(item.text, source); assert_parse_ok(R"({"old":false})", target); const std::string expected = compact_json(source); target = std::move(source); EXPECT_EQ(compact_json(target), expected); } } TEST(JSONTest, SelfCopyAssignmentKeepsComplexJson) { for (const JsonCase& item : json_cases()) { SCOPED_TRACE(item.name); Psc::JSON json; assert_parse_ok(item.text, json); const std::string expected = compact_json(json); json = json; EXPECT_EQ(compact_json(json), expected); } } TEST(JSONTest, GetMissingFieldReturnsError) { Psc::JSON json; assert_parse_ok(R"({"name":"psc"})", json); std::error_code ec; std::string value; EXPECT_FALSE(json.get_string_ec("missing", value, ec)); EXPECT_EQ(ec, std::make_error_code(std::errc::invalid_argument)); } TEST(JSONTest, GetBoolFromNonBoolReturnsError) { Psc::JSON json; assert_parse_ok(R"({"name":"psc"})", json); std::error_code ec; bool value = false; EXPECT_FALSE(json.get_bool_ec("name", value, ec)); EXPECT_EQ(ec, std::make_error_code(std::errc::invalid_argument)); } TEST(JSONTest, ParseInvalidJsonReturnsError) { Psc::JSON json; std::error_code ec; EXPECT_FALSE(Psc::parse_json_ec("x", json, ec)); EXPECT_EQ(ec, std::make_error_code(std::errc::invalid_argument)); } TEST(JSONTest, ParseJsonFileComplex) { constexpr const char *file_name = "psc_json_gtest_tmp.json"; TempFileGuard guard{file_name}; { std::ofstream ofs(file_name, std::ios::binary); ASSERT_TRUE(ofs.is_open()); ofs << R"({ "app": { "name": "file_test", "enabled": true, "items": [ {"id": 1, "name": "a"}, {"id": 2, "name": "b"} ] }, "ok": true })"; } Psc::JSON json; std::error_code ec; ASSERT_TRUE(Psc::parse_json_file_ec(file_name, json, ec)) << ec.message(); EXPECT_TRUE(json.has("app")); EXPECT_TRUE(json.has("ok")); bool ok = false; ASSERT_TRUE(json.get_bool_ec("ok", ok, ec)); EXPECT_TRUE(ok); const Psc::JSON *app = json.get("app"); ASSERT_NE(app, nullptr); std::string name; ASSERT_TRUE(app->get_string_ec("name", name, ec)); EXPECT_EQ(name, "file_test"); bool enabled = false; ASSERT_TRUE(app->get_bool_ec("enabled", enabled, ec)); EXPECT_TRUE(enabled); } TEST(JSONTest, ParseMissingFileReturnsError) { Psc::JSON json; std::error_code ec; EXPECT_FALSE(Psc::parse_json_file_ec("__not_exist_psc_json_test_file__.json", json, ec)); EXPECT_EQ(ec, std::make_error_code(std::errc::no_such_file_or_directory)); } #endif