943 lines
24 KiB
C++
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 |