305 lines
9.7 KiB
C++
305 lines
9.7 KiB
C++
// 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 <cctype>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
namespace rk3588 {
|
|
|
|
class SimpleJson {
|
|
public:
|
|
enum class Type { Null, Bool, Number, String, Array, Object };
|
|
using Array = std::vector<SimpleJson>;
|
|
using Object = std::map<std::string, SimpleJson>;
|
|
using Variant = std::variant<std::nullptr_t, bool, double, std::string, Array, Object>;
|
|
|
|
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<Type>(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<bool>(value_) ? std::get<bool>(value_) : def;
|
|
}
|
|
|
|
double AsNumber(double def = 0.0) const {
|
|
return std::holds_alternative<double>(value_) ? std::get<double>(value_) : def;
|
|
}
|
|
|
|
int AsInt(int def = 0) const {
|
|
return static_cast<int>(AsNumber(static_cast<double>(def)));
|
|
}
|
|
|
|
const std::string& AsString(const std::string& def = EmptyString()) const {
|
|
if (const auto* s = std::get_if<std::string>(&value_)) {
|
|
return *s;
|
|
}
|
|
return def;
|
|
}
|
|
|
|
const Array& AsArray() const {
|
|
static const Array kEmpty;
|
|
if (const auto* a = std::get_if<Array>(&value_)) {
|
|
return *a;
|
|
}
|
|
return kEmpty;
|
|
}
|
|
|
|
const Object& AsObject() const {
|
|
static const Object kEmpty;
|
|
if (const auto* o = std::get_if<Object>(&value_)) {
|
|
return *o;
|
|
}
|
|
return kEmpty;
|
|
}
|
|
|
|
const SimpleJson* Find(const std::string& key) const {
|
|
if (const auto* o = std::get_if<Object>(&value_)) {
|
|
auto it = o->find(key);
|
|
if (it != o->end()) {
|
|
return &it->second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template <typename T>
|
|
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<T, std::string>) {
|
|
return child->AsString(def);
|
|
} else if constexpr (std::is_same_v<T, double>) {
|
|
return child->AsNumber(def);
|
|
} else if constexpr (std::is_same_v<T, int>) {
|
|
return child->AsInt(def);
|
|
} else if constexpr (std::is_same_v<T, bool>) {
|
|
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<unsigned char>(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<unsigned char>(text[pos]))) ++pos;
|
|
if (pos < text.size() && text[pos] == '.') {
|
|
++pos;
|
|
while (pos < text.size() && std::isdigit(static_cast<unsigned char>(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<unsigned char>(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<unsigned char>(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
|