Files
dynagate-cpp/json.cpp
T

747 lines
16 KiB
C++
Raw Normal View History

2026-10-14 09:18:19 +03:00
/*
Dynagate native RFC 8259 JSON parser and serializer (C++20 port)
Created by Luxferre in 2026, released into the public domain
*/
#include "dynagate.hpp"
#include <cerrno>
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace {
void str_appendf(std::string &s, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
va_list cp;
va_copy(cp, ap);
int need = std::vsnprintf(nullptr, 0, fmt, cp);
va_end(cp);
if (need > 0) {
std::string more;
more.resize(static_cast<size_t>(need));
std::vsnprintf(more.data(), static_cast<size_t>(need) + 1, fmt, ap);
s += more;
}
va_end(ap);
}
int utf8_decode(const unsigned char *p, size_t avail, size_t *plen) {
if (avail == 0) {
*plen = 0;
return 0xFFFD;
}
unsigned char b0 = p[0];
if (b0 < 0x80) {
*plen = 1;
return b0;
}
if ((b0 & 0xE0) == 0xC0) {
if (avail < 2 || (p[1] & 0xC0) != 0x80) {
*plen = 1;
return 0xFFFD;
}
int cp = ((b0 & 0x1F) << 6) | (p[1] & 0x3F);
if (cp < 0x80) {
*plen = 1;
return 0xFFFD;
}
*plen = 2;
return cp;
}
if ((b0 & 0xF0) == 0xE0) {
if (avail < 3 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80) {
*plen = 1;
return 0xFFFD;
}
int cp = ((b0 & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF)) {
*plen = 1;
return 0xFFFD;
}
*plen = 3;
return cp;
}
if ((b0 & 0xF8) == 0xF0) {
if (avail < 4 || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 ||
(p[3] & 0xC0) != 0x80) {
*plen = 1;
return 0xFFFD;
}
int cp = ((b0 & 0x07) << 18) | ((p[1] & 0x3F) << 12) |
((p[2] & 0x3F) << 6) | (p[3] & 0x3F);
if (cp < 0x10000 || cp > 0x10FFFF) {
*plen = 1;
return 0xFFFD;
}
*plen = 4;
return cp;
}
*plen = 1;
return 0xFFFD;
}
const Json kNullJson{};
void dump_str(std::string &out, std::string_view s) {
out += '"';
const auto *p = reinterpret_cast<const unsigned char *>(s.data());
size_t avail = s.size();
while (avail > 0) {
unsigned char c = p[0];
if (c < 0x80) {
p++;
avail--;
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) {
str_appendf(out, "\\u%04x", static_cast<unsigned>(c));
} else {
out += static_cast<char>(c);
}
break;
}
} else {
size_t pl = 0;
int cp = utf8_decode(p, avail, &pl);
if (cp == 0xFFFD && pl == 1) {
out += "\xEF\xBF\xBD";
p++;
avail--;
} else {
out.append(reinterpret_cast<const char *>(p), pl);
p += pl;
avail -= pl;
}
}
}
out += '"';
}
void dump_rec(std::string &out, const Json &j, int indent, int depth) {
switch (j.type) {
case JType::Null:
out += "null";
break;
case JType::Bool:
out += (j.bval ? "true" : "false");
break;
case JType::Int:
str_appendf(out, "%lld", static_cast<long long>(j.ival));
break;
case JType::Float:
if (!std::isfinite(j.dval)) {
out += "null";
} else {
str_appendf(out, "%.15g", j.dval);
}
break;
case JType::Str:
dump_str(out, j.sval);
break;
case JType::Arr:
if (j.aval.empty()) {
out += "[]";
} else {
out += '[';
for (size_t i = 0; i < j.aval.size(); i++) {
if (i > 0) {
out += ',';
}
if (indent >= 0) {
out += '\n';
out.append(static_cast<size_t>(depth + 1) *
static_cast<size_t>(indent),
' ');
}
dump_rec(out, j.aval[i], indent, depth + 1);
}
if (indent >= 0) {
out += '\n';
out.append(static_cast<size_t>(depth) * static_cast<size_t>(indent),
' ');
}
out += ']';
}
break;
case JType::Obj:
if (j.oval.empty()) {
out += "{}";
} else {
out += '{';
for (size_t i = 0; i < j.oval.size(); i++) {
if (i > 0) {
out += ',';
}
if (indent >= 0) {
out += '\n';
out.append(static_cast<size_t>(depth + 1) *
static_cast<size_t>(indent),
' ');
}
dump_str(out, j.oval[i].first);
out += (indent >= 0) ? ": " : ":";
dump_rec(out, j.oval[i].second, indent, depth + 1);
}
if (indent >= 0) {
out += '\n';
out.append(static_cast<size_t>(depth) * static_cast<size_t>(indent),
' ');
}
out += '}';
}
break;
}
}
struct JParser {
std::string_view src;
size_t pos = 0;
void skip_ws() {
while (pos < src.size()) {
char c = src[pos];
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
pos++;
} else {
break;
}
}
}
static int hex_val(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
return -1;
}
bool parse_hex4(uint32_t &out) {
if (pos + 4 > src.size()) {
return false;
}
uint32_t v = 0;
for (int i = 0; i < 4; i++) {
int h = hex_val(src[pos + static_cast<size_t>(i)]);
if (h < 0) {
return false;
}
v = (v << 4) | static_cast<uint32_t>(h);
}
pos += 4;
out = v;
return true;
}
static void append_utf8(std::string &s, uint32_t cp) {
if (cp <= 0x7F) {
s += static_cast<char>(cp);
} else if (cp <= 0x7FF) {
s += static_cast<char>(0xC0 | (cp >> 6));
s += static_cast<char>(0x80 | (cp & 0x3F));
} else if (cp <= 0xFFFF) {
s += static_cast<char>(0xE0 | (cp >> 12));
s += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
s += static_cast<char>(0x80 | (cp & 0x3F));
} else if (cp <= 0x10FFFF) {
s += static_cast<char>(0xF0 | (cp >> 18));
s += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
s += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
s += static_cast<char>(0x80 | (cp & 0x3F));
} else {
s += "\xEF\xBF\xBD";
}
}
bool parse_string(std::string &out) {
if (pos >= src.size() || src[pos] != '"') {
return false;
}
pos++;
out.clear();
while (pos < src.size()) {
auto c = static_cast<unsigned char>(src[pos++]);
if (c == '"') {
return true;
}
if (c < 0x20) {
return false;
}
if (c != '\\') {
out += static_cast<char>(c);
continue;
}
if (pos >= src.size()) {
return false;
}
char esc = src[pos++];
switch (esc) {
case '"':
out += '"';
break;
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;
case 'u': {
uint32_t cp = 0;
if (!parse_hex4(cp)) {
return false;
}
if (cp >= 0xD800 && cp <= 0xDBFF) {
if (pos + 6 <= src.size() && src[pos] == '\\' &&
src[pos + 1] == 'u') {
size_t saved = pos;
pos += 2;
uint32_t low = 0;
if (parse_hex4(low) && low >= 0xDC00 && low <= 0xDFFF) {
cp = 0x10000u + (((cp - 0xD800u) << 10) | (low - 0xDC00u));
} else {
pos = saved;
cp = 0xFFFDu;
}
} else {
cp = 0xFFFDu;
}
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
cp = 0xFFFDu;
}
append_utf8(out, cp);
break;
}
default:
return false;
}
}
return false;
}
bool parse_number(Json &out) {
size_t start = pos;
if (src[pos] == '-') {
pos++;
}
if (pos >= src.size()) {
return false;
}
if (src[pos] == '0') {
pos++;
if (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') {
return false;
}
} else if (src[pos] >= '1' && src[pos] <= '9') {
while (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') {
pos++;
}
} else {
return false;
}
bool is_float = false;
if (pos < src.size() && src[pos] == '.') {
is_float = true;
pos++;
if (pos >= src.size() || src[pos] < '0' || src[pos] > '9') {
return false;
}
while (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') {
pos++;
}
}
if (pos < src.size() && (src[pos] == 'e' || src[pos] == 'E')) {
is_float = true;
pos++;
if (pos < src.size() && (src[pos] == '+' || src[pos] == '-')) {
pos++;
}
if (pos >= src.size() || src[pos] < '0' || src[pos] > '9') {
return false;
}
while (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') {
pos++;
}
}
std::string num_str(src.substr(start, pos - start));
if (!is_float) {
errno = 0;
char *end = nullptr;
long long iv = std::strtoll(num_str.c_str(), &end, 10);
if (errno == 0 && end == num_str.c_str() + num_str.size()) {
out = Json(static_cast<int64_t>(iv));
return true;
}
}
errno = 0;
char *end = nullptr;
double dv = std::strtod(num_str.c_str(), &end);
if (end != num_str.c_str() + num_str.size() || !std::isfinite(dv)) {
return false;
}
out = Json(dv);
return true;
}
bool parse_value(Json &out, int depth) {
if (depth > 256) {
return false;
}
skip_ws();
if (pos >= src.size()) {
return false;
}
char c = src[pos];
if (c == 'n') {
if (src.substr(pos, 4) != "null") {
return false;
}
pos += 4;
out = Json(nullptr);
return true;
}
if (c == 't') {
if (src.substr(pos, 4) != "true") {
return false;
}
pos += 4;
out = Json(true);
return true;
}
if (c == 'f') {
if (src.substr(pos, 5) != "false") {
return false;
}
pos += 5;
out = Json(false);
return true;
}
if (c == '"') {
std::string s;
if (!parse_string(s)) {
return false;
}
out = Json(std::move(s));
return true;
}
if (c == '[') {
pos++;
skip_ws();
out = Json::array();
if (pos < src.size() && src[pos] == ']') {
pos++;
return true;
}
while (true) {
Json elem;
if (!parse_value(elem, depth + 1)) {
return false;
}
out.aval.push_back(std::move(elem));
skip_ws();
if (pos < src.size() && src[pos] == ',') {
pos++;
continue;
}
if (pos < src.size() && src[pos] == ']') {
pos++;
return true;
}
return false;
}
}
if (c == '{') {
pos++;
skip_ws();
out = Json::object();
if (pos < src.size() && src[pos] == '}') {
pos++;
return true;
}
while (true) {
skip_ws();
std::string key;
if (!parse_string(key)) {
return false;
}
skip_ws();
if (pos >= src.size() || src[pos] != ':') {
return false;
}
pos++;
Json val;
if (!parse_value(val, depth + 1)) {
return false;
}
out[key] = std::move(val);
skip_ws();
if (pos < src.size() && src[pos] == ',') {
pos++;
continue;
}
if (pos < src.size() && src[pos] == '}') {
pos++;
return true;
}
return false;
}
}
if (c == '-' || (c >= '0' && c <= '9')) {
return parse_number(out);
}
return false;
}
};
} // namespace
Json::Json() = default;
Json::~Json() = default;
Json::Json(const Json &) = default;
Json::Json(Json &&) noexcept = default;
Json &Json::operator=(const Json &) = default;
Json &Json::operator=(Json &&) noexcept = default;
Json::Json(std::nullptr_t) : type(JType::Null) {}
Json::Json(bool b) : type(JType::Bool), bval(b) {}
Json::Json(int n) : type(JType::Int), ival(n), dval(n) {}
Json::Json(int64_t n)
: type(JType::Int), ival(n), dval(static_cast<double>(n)) {}
Json::Json(double d)
: type(JType::Float), ival(static_cast<int64_t>(d)), dval(d) {}
Json::Json(const char *s) : type(JType::Str), sval(s ? s : "") {}
Json::Json(std::string s) : type(JType::Str), sval(std::move(s)) {}
Json::Json(std::string_view s) : type(JType::Str), sval(s) {}
Json Json::object() {
Json j;
j.type = JType::Obj;
return j;
}
Json Json::object(
std::initializer_list<std::pair<std::string_view, Json>> init) {
Json j;
j.type = JType::Obj;
for (const auto &kv : init) {
j[kv.first] = kv.second;
}
return j;
}
Json Json::array() {
Json j;
j.type = JType::Arr;
return j;
}
Json Json::array(std::initializer_list<Json> init) {
Json j;
j.type = JType::Arr;
j.aval.assign(init.begin(), init.end());
return j;
}
bool Json::empty() const {
if (type == JType::Arr) {
return aval.empty();
}
if (type == JType::Obj) {
return oval.empty();
}
if (type == JType::Str) {
return sval.empty();
}
return true;
}
size_t Json::size() const {
if (type == JType::Arr) {
return aval.size();
}
if (type == JType::Obj) {
return oval.size();
}
if (type == JType::Str) {
return sval.size();
}
return 0;
}
bool Json::contains(std::string_view k) const {
if (type != JType::Obj) {
return false;
}
for (const auto &kv : oval) {
if (kv.first == k) {
return true;
}
}
return false;
}
void Json::erase(std::string_view k) {
if (type != JType::Obj) {
return;
}
for (auto it = oval.begin(); it != oval.end(); ++it) {
if (it->first == k) {
oval.erase(it);
return;
}
}
}
Json &Json::operator[](std::string_view k) {
if (type != JType::Obj) {
*this = Json::object();
}
for (auto &kv : oval) {
if (kv.first == k) {
return kv.second;
}
}
oval.emplace_back(std::string(k), Json{});
return oval.back().second;
}
const Json &Json::operator[](std::string_view k) const {
if (type != JType::Obj) {
return kNullJson;
}
for (const auto &kv : oval) {
if (kv.first == k) {
return kv.second;
}
}
return kNullJson;
}
Json &Json::at(size_t i) {
return aval[i];
}
const Json &Json::at(size_t i) const {
if (type != JType::Arr || i >= aval.size()) {
return kNullJson;
}
return aval[i];
}
void Json::push_back(Json v) {
if (type != JType::Arr) {
*this = Json::array();
}
aval.push_back(std::move(v));
}
std::string Json::str(std::string_view def) const {
if (type == JType::Str) {
return sval;
}
return std::string(def);
}
bool Json::as_bool(bool def) const {
if (type == JType::Bool) {
return bval;
}
return def;
}
int64_t Json::as_int64(int64_t def) const {
if (type == JType::Int) {
return ival;
}
if (type == JType::Float) {
return static_cast<int64_t>(dval);
}
return def;
}
int Json::as_int(int def) const {
if (type != JType::Int && type != JType::Float) {
return def;
}
int64_t v = (type == JType::Int) ? ival : static_cast<int64_t>(dval);
if (v > INT_MAX) {
return INT_MAX;
}
if (v < INT_MIN) {
return INT_MIN;
}
return static_cast<int>(v);
}
std::string Json::dump(int indent) const {
std::string out;
dump_rec(out, *this, indent, 0);
return out;
}
bool Json::parse(std::string_view src, Json &out) {
JParser p{src, 0};
Json tmp;
if (!p.parse_value(tmp, 0)) {
return false;
}
p.skip_ws();
if (p.pos != src.size()) {
return false;
}
out = std::move(tmp);
return true;
}
Json Json::parse(std::string_view src) {
Json out;
parse(src, out);
return out;
}