319 lines
11 KiB
C++
319 lines
11 KiB
C++
#include "json.hpp"
|
||
|
||
#include <cmath>
|
||
#include <cstdlib>
|
||
#include <iomanip>
|
||
#include <limits>
|
||
#include <sstream>
|
||
#include <stdexcept>
|
||
|
||
namespace connector {
|
||
namespace {
|
||
|
||
void append_utf8(std::string& out, unsigned codepoint) {
|
||
if (codepoint <= 0x7f) {
|
||
out.push_back(static_cast<char>(codepoint));
|
||
} else if (codepoint <= 0x7ff) {
|
||
out.push_back(static_cast<char>(0xc0 | (codepoint >> 6)));
|
||
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3f)));
|
||
} else if (codepoint <= 0xffff) {
|
||
out.push_back(static_cast<char>(0xe0 | (codepoint >> 12)));
|
||
out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
|
||
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3f)));
|
||
} else {
|
||
out.push_back(static_cast<char>(0xf0 | (codepoint >> 18)));
|
||
out.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f)));
|
||
out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
|
||
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3f)));
|
||
}
|
||
}
|
||
|
||
class Parser {
|
||
public:
|
||
explicit Parser(const std::string& text) : text_(text) {}
|
||
|
||
Json parse() {
|
||
skip_space();
|
||
Json result = value();
|
||
skip_space();
|
||
if (position_ != text_.size()) fail("JSON 末尾存在多余内容");
|
||
return result;
|
||
}
|
||
|
||
private:
|
||
const std::string& text_;
|
||
std::size_t position_ = 0;
|
||
|
||
[[noreturn]] void fail(const std::string& message) const {
|
||
throw std::runtime_error(message + "(位置 " + std::to_string(position_) + ")");
|
||
}
|
||
|
||
void skip_space() {
|
||
while (position_ < text_.size()) {
|
||
const char c = text_[position_];
|
||
if (c != ' ' && c != '\t' && c != '\r' && c != '\n') break;
|
||
++position_;
|
||
}
|
||
}
|
||
|
||
bool consume(char expected) {
|
||
skip_space();
|
||
if (position_ >= text_.size() || text_[position_] != expected) return false;
|
||
++position_;
|
||
return true;
|
||
}
|
||
|
||
void literal(const char* expected) {
|
||
while (*expected) {
|
||
if (position_ >= text_.size() || text_[position_++] != *expected++) fail("JSON 字面量无效");
|
||
}
|
||
}
|
||
|
||
unsigned hex4() {
|
||
unsigned value = 0;
|
||
for (int index = 0; index < 4; ++index) {
|
||
if (position_ >= text_.size()) fail("JSON Unicode 转义不完整");
|
||
const char c = text_[position_++];
|
||
value <<= 4;
|
||
if (c >= '0' && c <= '9') value |= static_cast<unsigned>(c - '0');
|
||
else if (c >= 'a' && c <= 'f') value |= static_cast<unsigned>(c - 'a' + 10);
|
||
else if (c >= 'A' && c <= 'F') value |= static_cast<unsigned>(c - 'A' + 10);
|
||
else fail("JSON Unicode 转义无效");
|
||
}
|
||
return value;
|
||
}
|
||
|
||
std::string string_value() {
|
||
if (!consume('"')) fail("JSON 字符串缺少引号");
|
||
std::string out;
|
||
while (position_ < text_.size()) {
|
||
const char c = text_[position_++];
|
||
if (c == '"') return out;
|
||
if (static_cast<unsigned char>(c) < 0x20) fail("JSON 字符串包含控制字符");
|
||
if (c != '\\') {
|
||
out.push_back(c);
|
||
continue;
|
||
}
|
||
if (position_ >= text_.size()) fail("JSON 转义不完整");
|
||
switch (text_[position_++]) {
|
||
case '"': out.push_back('"'); break;
|
||
case '\\': out.push_back('\\'); break;
|
||
case '/': out.push_back('/'); break;
|
||
case 'b': out.push_back('\b'); break;
|
||
case 'f': out.push_back('\f'); break;
|
||
case 'n': out.push_back('\n'); break;
|
||
case 'r': out.push_back('\r'); break;
|
||
case 't': out.push_back('\t'); break;
|
||
case 'u': {
|
||
unsigned codepoint = hex4();
|
||
if (codepoint >= 0xd800 && codepoint <= 0xdbff) {
|
||
if (position_ + 2 > text_.size() || text_[position_++] != '\\' || text_[position_++] != 'u') {
|
||
fail("JSON Unicode 代理对无效");
|
||
}
|
||
const unsigned low = hex4();
|
||
if (low < 0xdc00 || low > 0xdfff) fail("JSON Unicode 代理对无效");
|
||
codepoint = 0x10000 + ((codepoint - 0xd800) << 10) + (low - 0xdc00);
|
||
}
|
||
append_utf8(out, codepoint);
|
||
break;
|
||
}
|
||
default: fail("JSON 转义字符无效");
|
||
}
|
||
}
|
||
fail("JSON 字符串未结束");
|
||
}
|
||
|
||
Json number_value() {
|
||
const std::size_t start = position_;
|
||
if (position_ < text_.size() && text_[position_] == '-') ++position_;
|
||
if (position_ >= text_.size()) fail("JSON 数字无效");
|
||
if (text_[position_] == '0') {
|
||
++position_;
|
||
} else {
|
||
if (text_[position_] < '1' || text_[position_] > '9') fail("JSON 数字无效");
|
||
while (position_ < text_.size() && text_[position_] >= '0' && text_[position_] <= '9') ++position_;
|
||
}
|
||
if (position_ < text_.size() && text_[position_] == '.') {
|
||
++position_;
|
||
if (position_ >= text_.size() || text_[position_] < '0' || text_[position_] > '9') fail("JSON 小数无效");
|
||
while (position_ < text_.size() && text_[position_] >= '0' && text_[position_] <= '9') ++position_;
|
||
}
|
||
if (position_ < text_.size() && (text_[position_] == 'e' || text_[position_] == 'E')) {
|
||
++position_;
|
||
if (position_ < text_.size() && (text_[position_] == '+' || text_[position_] == '-')) ++position_;
|
||
if (position_ >= text_.size() || text_[position_] < '0' || text_[position_] > '9') fail("JSON 指数无效");
|
||
while (position_ < text_.size() && text_[position_] >= '0' && text_[position_] <= '9') ++position_;
|
||
}
|
||
const std::string token = text_.substr(start, position_ - start);
|
||
char* end = nullptr;
|
||
const double parsed = std::strtod(token.c_str(), &end);
|
||
if (!end || *end || !std::isfinite(parsed)) fail("JSON 数字超出范围");
|
||
return Json(parsed);
|
||
}
|
||
|
||
Json array_value() {
|
||
consume('[');
|
||
Json::Array values;
|
||
if (consume(']')) return Json(std::move(values));
|
||
while (true) {
|
||
values.push_back(value());
|
||
if (consume(']')) break;
|
||
if (!consume(',')) fail("JSON 数组缺少逗号");
|
||
}
|
||
return Json(std::move(values));
|
||
}
|
||
|
||
Json object_value() {
|
||
consume('{');
|
||
Json::Object values;
|
||
if (consume('}')) return Json(std::move(values));
|
||
while (true) {
|
||
skip_space();
|
||
if (position_ >= text_.size() || text_[position_] != '"') fail("JSON 对象键名无效");
|
||
const std::string key = string_value();
|
||
if (!consume(':')) fail("JSON 对象缺少冒号");
|
||
values[key] = value();
|
||
if (consume('}')) break;
|
||
if (!consume(',')) fail("JSON 对象缺少逗号");
|
||
}
|
||
return Json(std::move(values));
|
||
}
|
||
|
||
Json value() {
|
||
skip_space();
|
||
if (position_ >= text_.size()) fail("JSON 内容不完整");
|
||
switch (text_[position_]) {
|
||
case 'n': literal("null"); return Json();
|
||
case 't': literal("true"); return Json(true);
|
||
case 'f': literal("false"); return Json(false);
|
||
case '"': return Json(string_value());
|
||
case '[': return array_value();
|
||
case '{': return object_value();
|
||
default: return number_value();
|
||
}
|
||
}
|
||
};
|
||
|
||
std::string indent_text(int indent, int depth) {
|
||
return indent > 0 ? std::string(static_cast<std::size_t>(indent * depth), ' ') : std::string();
|
||
}
|
||
|
||
void dump_string(std::ostringstream& out, const std::string& value) {
|
||
out << '"';
|
||
for (const unsigned char c : value) {
|
||
switch (c) {
|
||
case '"': out << "\\\""; break;
|
||
case '\\': out << "\\\\"; break;
|
||
case '\b': out << "\\b"; break;
|
||
case '\f': out << "\\f"; break;
|
||
case '\n': out << "\\n"; break;
|
||
case '\r': out << "\\r"; break;
|
||
case '\t': out << "\\t"; break;
|
||
default:
|
||
if (c < 0x20) {
|
||
out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(c)
|
||
<< std::dec << std::setfill(' ');
|
||
} else {
|
||
out << static_cast<char>(c);
|
||
}
|
||
}
|
||
}
|
||
out << '"';
|
||
}
|
||
|
||
void dump_value(std::ostringstream& out, const Json& value, int indent, int depth) {
|
||
if (value.is_null()) {
|
||
out << "null";
|
||
} else if (value.is_bool()) {
|
||
out << (value.boolean() ? "true" : "false");
|
||
} else if (value.is_number()) {
|
||
out << std::setprecision(std::numeric_limits<double>::max_digits10) << value.number();
|
||
} else if (value.is_string()) {
|
||
dump_string(out, value.string());
|
||
} else if (value.is_array()) {
|
||
out << '[';
|
||
const auto& values = value.array();
|
||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||
if (index) out << ',';
|
||
if (indent > 0) out << '\n' << indent_text(indent, depth + 1);
|
||
dump_value(out, values[index], indent, depth + 1);
|
||
}
|
||
if (indent > 0 && !values.empty()) out << '\n' << indent_text(indent, depth);
|
||
out << ']';
|
||
} else {
|
||
out << '{';
|
||
const auto& values = value.object();
|
||
std::size_t index = 0;
|
||
for (const auto& [key, child] : values) {
|
||
if (index++) out << ',';
|
||
if (indent > 0) out << '\n' << indent_text(indent, depth + 1);
|
||
dump_string(out, key);
|
||
out << (indent > 0 ? ": " : ":");
|
||
dump_value(out, child, indent, depth + 1);
|
||
}
|
||
if (indent > 0 && !values.empty()) out << '\n' << indent_text(indent, depth);
|
||
out << '}';
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
|
||
Json::Json() : value_(nullptr) {}
|
||
Json::Json(std::nullptr_t) : value_(nullptr) {}
|
||
Json::Json(bool value) : value_(value) {}
|
||
Json::Json(double value) : value_(value) {}
|
||
Json::Json(const char* value) : value_(std::string(value ? value : "")) {}
|
||
Json::Json(std::string value) : value_(std::move(value)) {}
|
||
Json::Json(Array value) : value_(std::move(value)) {}
|
||
Json::Json(Object value) : value_(std::move(value)) {}
|
||
|
||
Json Json::parse(const std::string& text) { return Parser(text).parse(); }
|
||
|
||
std::string Json::dump(int indent) const {
|
||
std::ostringstream out;
|
||
dump_value(out, *this, indent, 0);
|
||
return out.str();
|
||
}
|
||
|
||
bool Json::is_null() const { return std::holds_alternative<std::nullptr_t>(value_); }
|
||
bool Json::is_bool() const { return std::holds_alternative<bool>(value_); }
|
||
bool Json::is_number() const { return std::holds_alternative<double>(value_); }
|
||
bool Json::is_string() const { return std::holds_alternative<std::string>(value_); }
|
||
bool Json::is_array() const { return std::holds_alternative<Array>(value_); }
|
||
bool Json::is_object() const { return std::holds_alternative<Object>(value_); }
|
||
bool Json::boolean(bool fallback) const { return is_bool() ? std::get<bool>(value_) : fallback; }
|
||
double Json::number(double fallback) const { return is_number() ? std::get<double>(value_) : fallback; }
|
||
const std::string& Json::string() const {
|
||
static const std::string empty;
|
||
return is_string() ? std::get<std::string>(value_) : empty;
|
||
}
|
||
const Json::Array& Json::array() const {
|
||
static const Array empty;
|
||
return is_array() ? std::get<Array>(value_) : empty;
|
||
}
|
||
Json::Array& Json::array() {
|
||
if (!is_array()) value_ = Array{};
|
||
return std::get<Array>(value_);
|
||
}
|
||
const Json::Object& Json::object() const {
|
||
static const Object empty;
|
||
return is_object() ? std::get<Object>(value_) : empty;
|
||
}
|
||
Json::Object& Json::object() {
|
||
if (!is_object()) value_ = Object{};
|
||
return std::get<Object>(value_);
|
||
}
|
||
const Json* Json::find(const std::string& key) const {
|
||
if (!is_object()) return nullptr;
|
||
const auto found = object().find(key);
|
||
return found == object().end() ? nullptr : &found->second;
|
||
}
|
||
Json* Json::find(const std::string& key) {
|
||
if (!is_object()) return nullptr;
|
||
const auto found = object().find(key);
|
||
return found == object().end() ? nullptr : &found->second;
|
||
}
|
||
Json& Json::operator[](const std::string& key) { return object()[key]; }
|
||
|
||
} // namespace connector
|