Files
2026-10-02 14:40:41 -05:00

174 lines
8.2 KiB
C++

#pragma once
#include <algorithm>
#include <concepts>
#include <cstdint>
#include <initializer_list>
#include <iterator>
#include <map>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace dingosdk {
struct JsonLimits {
std::size_t bytes = 4 * 1024 * 1024;
std::size_t depth = 32;
std::size_t events = 16384 * 16;
};
// Owning profile values with deep copies and stable object-member references.
// RapidJSON's SAX reader/writers handle JSON syntax in json.cpp; this deliberately
// small value model keeps allocator lifetimes out of snapshots and runtime code.
class Json {
public:
using Object = std::map<std::string, Json, std::less<>>;
using Array = std::vector<Json>;
Json() = default;
Json(const Json&) = default;
Json(Json&&) noexcept = default;
// Copy before replacing storage, including assignment from a child value.
Json& operator=(Json value) noexcept { data_.swap(value.data_); return *this; }
Json(std::nullptr_t) {}
Json(bool value) : data_(value) {}
template<std::integral T> requires (!std::same_as<T, bool>)
Json(T value) {
if constexpr (std::is_signed_v<T>) data_ = static_cast<std::int64_t>(value);
else data_ = static_cast<std::uint64_t>(value);
}
template<std::floating_point T> Json(T value) : data_(static_cast<double>(value)) {}
Json(const char* value) : data_(std::string(value)) {}
Json(std::string value) : data_(std::move(value)) {}
Json(std::string_view value) : data_(std::string(value)) {}
Json(std::initializer_list<Json> values);
template<std::ranges::input_range R>
requires (!std::convertible_to<R, std::string_view> && !std::same_as<R, Json>)
Json(const R& values) : data_(Array{}) {
for (const auto& value : values) std::get<Array>(data_).emplace_back(value);
}
static Json object() { Json value; value.data_ = Object{}; return value; }
static Json array(std::initializer_list<Json> values = {}) {
Json value; value.data_ = Array(values); return value;
}
static Json parse(std::string_view text, JsonLimits limits = {});
template<std::input_iterator It> static Json parse(It first, It last) {
return parse(std::string(first, last));
}
std::string dump(int indent = -1) const;
bool is_null() const { return std::holds_alternative<std::monostate>(data_); }
bool is_boolean() const { return std::holds_alternative<bool>(data_); }
bool is_number_unsigned() const { return std::holds_alternative<std::uint64_t>(data_); }
bool is_number_integer() const { return is_number_unsigned() || std::holds_alternative<std::int64_t>(data_); }
bool is_number_float() const { return std::holds_alternative<double>(data_); }
bool is_number() const { return is_number_integer() || is_number_float(); }
bool is_string() const { return std::holds_alternative<std::string>(data_); }
bool is_array() const { return std::holds_alternative<Array>(data_); }
bool is_object() const { return std::holds_alternative<Object>(data_); }
std::size_t size() const {
if (is_object()) return std::get<Object>(data_).size();
if (is_array()) return std::get<Array>(data_).size();
return is_null() ? 0 : 1;
}
bool empty() const { return size() == 0; }
const std::string& string() const { return std::get<std::string>(data_); }
template<class T> T get() const {
if constexpr (std::same_as<T, Json>) return *this;
else if constexpr (std::same_as<T, std::string>) return string();
else if constexpr (std::same_as<T, bool>) return std::get<bool>(data_);
else if constexpr (std::is_arithmetic_v<T>) {
if (is_number_unsigned()) return static_cast<T>(std::get<std::uint64_t>(data_));
if (std::holds_alternative<std::int64_t>(data_)) return static_cast<T>(std::get<std::int64_t>(data_));
if (is_number_float()) return static_cast<T>(std::get<double>(data_));
throw std::runtime_error("JSON value must be numeric");
} else static_assert(sizeof(T) == 0, "Unsupported JSON conversion");
}
bool contains(std::string_view key) const {
return is_object() && std::get<Object>(data_).contains(key);
}
const Json& at(std::string_view key) const {
const auto& fields = std::get<Object>(data_);
const auto found = fields.find(key);
if (found == fields.end()) throw std::out_of_range("Missing JSON field: " + std::string(key));
return found->second;
}
Json& at(std::string_view key) { return const_cast<Json&>(std::as_const(*this).at(key)); }
const Json& at(std::size_t index) const { return std::get<Array>(data_).at(index); }
Json& at(std::size_t index) { return std::get<Array>(data_).at(index); }
Json& operator[](std::string_view key) {
if (is_null()) data_ = Object{};
return std::get<Object>(data_)[std::string(key)];
}
const Json& operator[](std::string_view key) const { return at(key); }
Json& operator[](std::size_t index) { return at(index); }
const Json& operator[](std::size_t index) const { return at(index); }
template<class T> T value(std::string_view key, const T& fallback) const {
if (!is_object()) throw std::runtime_error("JSON value must be an object");
return contains(key) ? at(key).get<T>() : fallback;
}
std::string value(std::string_view key, const char* fallback) const {
return value(key, std::string(fallback));
}
Object& items() { return std::get<Object>(data_); }
const Object& items() const { return std::get<Object>(data_); }
std::size_t erase(std::string_view key) { return items().erase(std::string(key)); }
void push_back(Json value) {
if (is_null()) data_ = Array{};
std::get<Array>(data_).push_back(std::move(value));
}
template<bool Const> class Iterator {
using Owner = std::conditional_t<Const, const Json, Json>;
using Member = std::conditional_t<Const, Object::const_iterator, Object::iterator>;
Owner* owner_{};
Member member_{};
std::size_t index_{};
friend class Json;
Iterator(Owner* owner, bool end) : owner_(owner), index_(end ? owner->size() : 0) {
if (owner->is_object()) member_ = end ? owner->items().end() : owner->items().begin();
}
public:
using value_type = Json;
using difference_type = std::ptrdiff_t;
using reference = Owner&;
using pointer = Owner*;
using iterator_category = std::forward_iterator_tag;
Iterator() = default;
reference operator*() const { return owner_->is_object() ? member_->second : owner_->at(index_); }
pointer operator->() const { return &**this; }
Iterator& operator++() { if (owner_->is_object()) ++member_; else ++index_; return *this; }
Iterator operator++(int) { auto old = *this; ++*this; return old; }
bool operator==(const Iterator& other) const {
if (owner_ != other.owner_) return false;
return owner_ && owner_->is_object() ? member_ == other.member_ : index_ == other.index_;
}
};
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
iterator begin() { return iterator(this, false); }
iterator end() { return iterator(this, true); }
const_iterator begin() const { return const_iterator(this, false); }
const_iterator end() const { return const_iterator(this, true); }
iterator find(std::string_view key) {
auto result = end(); if (is_object()) result.member_ = items().find(key); return result;
}
const_iterator find(std::string_view key) const {
auto result = end(); if (is_object()) result.member_ = items().find(key); return result;
}
bool operator==(const Json& other) const;
bool operator<(const Json& other) const;
bool operator>(const Json& other) const { return other < *this; }
bool operator<=(const Json& other) const { return *this == other || *this < other; }
bool operator>=(const Json& other) const { return *this == other || other < *this; }
private:
std::variant<std::monostate, bool, std::int64_t, std::uint64_t, double, std::string, Array, Object> data_;
};
}