OrangePi3588Media/include/utils/simple_json.h

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