// Minimal JSON DOM and parser for configuration use. // Supports objects, arrays, strings, numbers, booleans, and null. // Not a full JSON implementation but sufficient for structured config files. #pragma once #include #include #include #include #include #include #include namespace rk3588 { class SimpleJson { public: enum class Type { Null, Bool, Number, String, Array, Object }; using Array = std::vector; using Object = std::map; using Variant = std::variant; SimpleJson() : value_(nullptr) {} explicit SimpleJson(std::nullptr_t) : value_(nullptr) {} explicit SimpleJson(bool b) : value_(b) {} explicit SimpleJson(double n) : value_(n) {} explicit SimpleJson(std::string s) : value_(std::move(s)) {} explicit SimpleJson(Array a) : value_(std::move(a)) {} explicit SimpleJson(Object o) : value_(std::move(o)) {} Type type() const { return static_cast(value_.index()); } bool IsNull() const { return type() == Type::Null; } bool IsBool() const { return type() == Type::Bool; } bool IsNumber() const { return type() == Type::Number; } bool IsString() const { return type() == Type::String; } bool IsArray() const { return type() == Type::Array; } bool IsObject() const { return type() == Type::Object; } bool AsBool(bool def = false) const { return std::holds_alternative(value_) ? std::get(value_) : def; } double AsNumber(double def = 0.0) const { return std::holds_alternative(value_) ? std::get(value_) : def; } int AsInt(int def = 0) const { return static_cast(AsNumber(static_cast(def))); } const std::string& AsString(const std::string& def = EmptyString()) const { if (const auto* s = std::get_if(&value_)) { return *s; } return def; } const Array& AsArray() const { static const Array kEmpty; if (const auto* a = std::get_if(&value_)) { return *a; } return kEmpty; } const Object& AsObject() const { static const Object kEmpty; if (const auto* o = std::get_if(&value_)) { return *o; } return kEmpty; } const SimpleJson* Find(const std::string& key) const { if (const auto* o = std::get_if(&value_)) { auto it = o->find(key); if (it != o->end()) { return &it->second; } } return nullptr; } template T ValueOr(const std::string& key, const T& def) const { const SimpleJson* child = Find(key); if (!child) return def; if constexpr (std::is_same_v) { return child->AsString(def); } else if constexpr (std::is_same_v) { return child->AsNumber(def); } else if constexpr (std::is_same_v) { return child->AsInt(def); } else if constexpr (std::is_same_v) { return child->AsBool(def); } else { return def; } } private: Variant value_; static const std::string& EmptyString() { static const std::string empty; return empty; } }; inline void SkipWhitespace(std::string_view text, size_t& pos) { while (pos < text.size() && std::isspace(static_cast(text[pos]))) { ++pos; } } inline bool ConsumeLiteral(std::string_view text, size_t& pos, std::string_view lit) { if (text.substr(pos, lit.size()) == lit) { pos += lit.size(); return true; } return false; } inline bool ParseString(std::string_view text, size_t& pos, std::string& out, std::string& err) { if (text[pos] != '"') return false; ++pos; // skip opening quote while (pos < text.size()) { char c = text[pos++]; if (c == '"') { return true; } if (c == '\\') { if (pos >= text.size()) { err = "Unexpected end of input in string escape"; return false; } char esc = text[pos++]; switch (esc) { 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; default: err = "Unsupported escape sequence"; return false; } } else { out.push_back(c); } } err = "Unterminated string"; return false; } inline bool ParseNumber(std::string_view text, size_t& pos, double& out) { size_t start = pos; if (text[pos] == '-') ++pos; while (pos < text.size() && std::isdigit(static_cast(text[pos]))) ++pos; if (pos < text.size() && text[pos] == '.') { ++pos; while (pos < text.size() && std::isdigit(static_cast(text[pos]))) ++pos; } if (pos < text.size() && (text[pos] == 'e' || text[pos] == 'E')) { ++pos; if (pos < text.size() && (text[pos] == '+' || text[pos] == '-')) ++pos; while (pos < text.size() && std::isdigit(static_cast(text[pos]))) ++pos; } try { out = std::stod(std::string{text.substr(start, pos - start)}); return true; } catch (...) { return false; } } inline bool ParseValue(std::string_view text, size_t& pos, SimpleJson& out, std::string& err); inline bool ParseArray(std::string_view text, size_t& pos, SimpleJson& out, std::string& err) { if (text[pos] != '[') return false; ++pos; SkipWhitespace(text, pos); SimpleJson::Array arr; if (pos < text.size() && text[pos] == ']') { ++pos; out = SimpleJson(std::move(arr)); return true; } while (pos < text.size()) { SimpleJson element; if (!ParseValue(text, pos, element, err)) return false; arr.push_back(std::move(element)); SkipWhitespace(text, pos); if (pos < text.size() && text[pos] == ',') { ++pos; SkipWhitespace(text, pos); continue; } if (pos < text.size() && text[pos] == ']') { ++pos; out = SimpleJson(std::move(arr)); return true; } err = "Expected ',' or ']' in array"; return false; } err = "Unterminated array"; return false; } inline bool ParseObject(std::string_view text, size_t& pos, SimpleJson& out, std::string& err) { if (text[pos] != '{') return false; ++pos; SkipWhitespace(text, pos); SimpleJson::Object obj; if (pos < text.size() && text[pos] == '}') { ++pos; out = SimpleJson(std::move(obj)); return true; } while (pos < text.size()) { SkipWhitespace(text, pos); if (pos >= text.size() || text[pos] != '"') { err = "Expected string key"; return false; } std::string key; if (!ParseString(text, pos, key, err)) return false; SkipWhitespace(text, pos); if (pos >= text.size() || text[pos] != ':') { err = "Expected ':' after object key"; return false; } ++pos; SkipWhitespace(text, pos); SimpleJson value; if (!ParseValue(text, pos, value, err)) return false; obj.emplace(std::move(key), std::move(value)); SkipWhitespace(text, pos); if (pos < text.size() && text[pos] == ',') { ++pos; SkipWhitespace(text, pos); continue; } if (pos < text.size() && text[pos] == '}') { ++pos; out = SimpleJson(std::move(obj)); return true; } err = "Expected ',' or '}' in object"; return false; } err = "Unterminated object"; return false; } inline bool ParseValue(std::string_view text, size_t& pos, SimpleJson& out, std::string& err) { SkipWhitespace(text, pos); if (pos >= text.size()) { err = "Unexpected end of input"; return false; } char c = text[pos]; if (c == 'n') { if (ConsumeLiteral(text, pos, "null")) { out = SimpleJson(nullptr); return true; } } else if (c == 't') { if (ConsumeLiteral(text, pos, "true")) { out = SimpleJson(true); return true; } } else if (c == 'f') { if (ConsumeLiteral(text, pos, "false")) { out = SimpleJson(false); return true; } } else if (c == '"') { std::string s; if (ParseString(text, pos, s, err)) { out = SimpleJson(std::move(s)); return true; } return false; } else if (c == '[') { return ParseArray(text, pos, out, err); } else if (c == '{') { return ParseObject(text, pos, out, err); } else if (c == '-' || std::isdigit(static_cast(c))) { double num = 0.0; if (ParseNumber(text, pos, num)) { out = SimpleJson(num); return true; } } err = "Invalid JSON value"; return false; } inline bool ParseSimpleJson(const std::string& input, SimpleJson& out, std::string& err) { size_t pos = 0; if (!ParseValue(input, pos, out, err)) return false; SkipWhitespace(input, pos); if (pos != input.size()) { err = "Unexpected characters after JSON document"; return false; } return true; } } // namespace rk3588