#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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>; using Array = std::vector; 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 requires (!std::same_as) Json(T value) { if constexpr (std::is_signed_v) data_ = static_cast(value); else data_ = static_cast(value); } template Json(T value) : data_(static_cast(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 values); template requires (!std::convertible_to && !std::same_as) Json(const R& values) : data_(Array{}) { for (const auto& value : values) std::get(data_).emplace_back(value); } static Json object() { Json value; value.data_ = Object{}; return value; } static Json array(std::initializer_list values = {}) { Json value; value.data_ = Array(values); return value; } static Json parse(std::string_view text, JsonLimits limits = {}); template 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(data_); } bool is_boolean() const { return std::holds_alternative(data_); } bool is_number_unsigned() const { return std::holds_alternative(data_); } bool is_number_integer() const { return is_number_unsigned() || std::holds_alternative(data_); } bool is_number_float() const { return std::holds_alternative(data_); } bool is_number() const { return is_number_integer() || is_number_float(); } bool is_string() const { return std::holds_alternative(data_); } bool is_array() const { return std::holds_alternative(data_); } bool is_object() const { return std::holds_alternative(data_); } std::size_t size() const { if (is_object()) return std::get(data_).size(); if (is_array()) return std::get(data_).size(); return is_null() ? 0 : 1; } bool empty() const { return size() == 0; } const std::string& string() const { return std::get(data_); } template T get() const { if constexpr (std::same_as) return *this; else if constexpr (std::same_as) return string(); else if constexpr (std::same_as) return std::get(data_); else if constexpr (std::is_arithmetic_v) { if (is_number_unsigned()) return static_cast(std::get(data_)); if (std::holds_alternative(data_)) return static_cast(std::get(data_)); if (is_number_float()) return static_cast(std::get(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(data_).contains(key); } const Json& at(std::string_view key) const { const auto& fields = std::get(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(std::as_const(*this).at(key)); } const Json& at(std::size_t index) const { return std::get(data_).at(index); } Json& at(std::size_t index) { return std::get(data_).at(index); } Json& operator[](std::string_view key) { if (is_null()) data_ = Object{}; return std::get(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 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() : fallback; } std::string value(std::string_view key, const char* fallback) const { return value(key, std::string(fallback)); } Object& items() { return std::get(data_); } const Object& items() const { return std::get(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(data_).push_back(std::move(value)); } template class Iterator { using Owner = std::conditional_t; using Member = std::conditional_t; 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; using const_iterator = Iterator; 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 data_; }; }