Files
CPP_Core/Core/Base/JSON.cpp
T
2026-06-25 15:44:25 +08:00

943 lines
24 KiB
C++

#include "JSON.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <utility>
#define HANDLE_ERROR(reason) \
JSON::handle_error(__func__, reason, __FILE__, __LINE__);
namespace Psc {
std::function<void(const char *, const std::string &, const char *, int)>
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<JSON &>(obj).to_json_string();
return os;
}
JSON::operator std::string() const {
return const_cast<JSON *>(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) {
// std::cout << "get" "bool" " error json not found! key:" << _key <<
// " json: " << to_json_string() << std::endl;
ec = std::make_error_code(std::errc::invalid_argument);
return false;
}
return that->bool_val_ec(out, ec);
}
// JSON::JSON(std::string key, const char* val) : key(std::move(key)),
// val(std::string(val)), valueType(String) {
// }
//
// JSON::JSON(std::string key, std::string val) : key(std::move(key)),
// val(std::move(val)), valueType(String) {
// }
//
// JSON::JSON(std::string key, bool val) : key(std::move(key)), val(val ? "true"
// : "false"), valueType(Bool) {
// }
//
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<JSON> &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<JSON> &children) {
return array("", children);
}
JSON JSON::object(const std::vector<JSON> &children) {
return object("", children);
}
JSON JSON::array(std::string key, const std::vector<JSON> &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<JSON> &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<int>(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;
// ret.append(split);
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;
}
// 解析的分割线----------------------------
// 如果未找到非空白字符,将 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<std::string &>(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<size_t>(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 <cstdio>
#include <fstream>
#include <gtest/gtest.h>
#include <string>
#include <string_view>
#include <system_error>
#include <vector>
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<JsonCase> &json_cases() {
static const std::vector<JsonCase> 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