From 76364591e1b5ece080742ff310591563e911f75b Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Fri, 21 Aug 2026 18:03:55 +0200 Subject: [PATCH] add common/json --- common/json.cpp | 230 +++++++++++++++++++++++++++++++++++++++++++++++ common/json.h | 234 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 464 insertions(+) create mode 100644 common/json.cpp create mode 100644 common/json.h diff --git a/common/json.cpp b/common/json.cpp new file mode 100644 index 0000000000..aada499fe6 --- /dev/null +++ b/common/json.cpp @@ -0,0 +1,230 @@ +#include "json.h" + +#include "ggml.h" + +#define JSON_ASSERT GGML_ASSERT +#include + +#include +#include + +using nlohmann::ordered_json; + +// common_json_node is never defined, it only stands for an ordered_json in this file +static ordered_json & as_json(common_json_node * node) { + return *reinterpret_cast(node); +} + +static const ordered_json & as_json(const common_json_node * node) { + return *reinterpret_cast(node); +} + +static common_json_node * as_node(ordered_json * json) { + return reinterpret_cast(json); +} + +void common_json_node_deleter::operator()(common_json_node * node) const { + delete reinterpret_cast(node); +} + +static common_json make_json(const ordered_json & val) { + common_json out; + as_json(out.get_node()) = val; + return out; +} + +static ordered_json to_json(const common_json_value & val) { + switch (val.type) { + case common_json_value::VAL_NULL: return nullptr; + case common_json_value::VAL_BOOL: return val.val_bool; + case common_json_value::VAL_INT: return val.val_int; + case common_json_value::VAL_UINT: return val.val_uint; + case common_json_value::VAL_DOUBLE: return val.val_double; + case common_json_value::VAL_STRING: return val.val_string; + case common_json_value::VAL_JSON: return as_json(val.val_json->get_node()); + } + + return nullptr; +} + +template T & common_json_raw(common_json_ref & json) { + return as_json(json.get_node()); +} + +template const T & common_json_raw(const common_json_ref & json) { + return as_json(json.get_node()); +} + +template common_json common_json_from_raw(const T & json) { + return make_json(json); +} + +// the bridge is usable only for the type below +template ordered_json & common_json_raw(common_json_ref &); +template const ordered_json & common_json_raw(const common_json_ref &); +template common_json common_json_from_raw(const ordered_json &); + +common_json_value::common_json_value(const char * val) { + if (val) { + type = VAL_STRING; + val_string = val; + } else { + type = VAL_NULL; + } +} + +common_json_value::common_json_value(const common_json & val) : + type(VAL_JSON), val_json(std::make_shared(val)) {} + +common_json_value::common_json_value(const common_json_ref & val) : + type(VAL_JSON), val_json(std::make_shared(make_json(as_json(val.get_node())))) {} + +bool common_json_ref::is_null() const { return as_json(node).is_null(); } +bool common_json_ref::is_object() const { return as_json(node).is_object(); } +bool common_json_ref::is_array() const { return as_json(node).is_array(); } +bool common_json_ref::is_string() const { return as_json(node).is_string(); } +bool common_json_ref::is_boolean() const { return as_json(node).is_boolean(); } +bool common_json_ref::is_number() const { return as_json(node).is_number(); } +bool common_json_ref::is_number_integer() const { return as_json(node).is_number_integer(); } + +bool common_json_ref::empty() const { return as_json(node).empty(); } +size_t common_json_ref::size() const { return as_json(node).size(); } + +bool common_json_ref::contains(const std::string & key) const { + return as_json(node).contains(key); +} + +bool common_json_ref::operator==(const common_json_value & val) const { + return as_json(node) == to_json(val); +} + +bool common_json_ref::operator!=(const common_json_value & val) const { + return !(*this == val); +} + +common_json_ref common_json_ref::at(const std::string & key) const { + return common_json_ref(as_node(&as_json(node).at(key))); +} + +common_json_ref common_json_ref::operator[](const std::string & key) const { + return common_json_ref(as_node(&as_json(node)[key])); +} + +common_json_ref common_json_ref::operator[](size_t idx) const { + return common_json_ref(as_node(&as_json(node)[idx])); +} + +void common_json_ref::assign(const common_json_value & val) { + as_json(node) = to_json(val); +} + +void common_json_ref::set(const common_json_item & item) { + as_json(node)[item.key] = to_json(item.val); +} + +void common_json_ref::push_back(const common_json_value & val) { + as_json(node).push_back(to_json(val)); +} + +std::string common_json_ref::dump(int indent) const { + return as_json(node).dump(indent); +} + +// an array is indexed directly, an object needs a walk from the start +common_json_ref common_json_ref::iterator::operator*() const { + if (as_json(node).is_object()) { + return common_json_ref(as_node(&std::next(as_json(node).begin(), idx).value())); + } + + return common_json_ref(as_node(&as_json(node)[idx])); +} + +std::string common_json_ref::iterator::key() const { + return std::next(as_json(node).begin(), idx).key(); +} + +std::pair common_json_ref::items_view::iterator::operator*() const { + auto it = std::next(as_json(node).begin(), idx); + + return { it.key(), common_json_ref(as_node(&it.value())) }; +} + +common_json::common_json() : + common_json_ref(nullptr), pimpl(as_node(new ordered_json(ordered_json::object()))) { + node = pimpl.get(); +} + +common_json::common_json(std::initializer_list items) : common_json() { + for (const auto & item : items) { + set(item); + } +} + +common_json::common_json(const common_json & other) : + common_json_ref(nullptr), pimpl(as_node(new ordered_json(as_json(other.node)))) { + node = pimpl.get(); +} + +common_json::common_json(common_json && other) noexcept : + common_json_ref(other.node), pimpl(std::move(other.pimpl)) { + other.node = nullptr; +} + +common_json & common_json::operator=(const common_json & other) { + as_json(node) = as_json(other.node); + + return *this; +} + +common_json & common_json::operator=(common_json && other) noexcept { + pimpl = std::move(other.pimpl); + node = pimpl.get(); + + other.node = nullptr; + + return *this; +} + +common_json::~common_json() = default; + +common_json common_json::parse(const std::string & text) { + try { + return make_json(ordered_json::parse(text)); + } catch (const std::exception & e) { + throw common_json_error(e.what()); + } +} + +common_json common_json::array() { + return make_json(ordered_json::array()); +} + +common_json common_json::object() { + return common_json(); +} + +common_json common_json::make(const common_json_value & val) { + return make_json(to_json(val)); +} + +template T common_json_ref::get() const { + return as_json(node).get(); +} + +// get() is usable only for the types below + +#define COMMON_JSON_GET(...) template __VA_ARGS__ common_json_ref::get<__VA_ARGS__>() const; + +COMMON_JSON_GET(bool) +COMMON_JSON_GET(int) +COMMON_JSON_GET(unsigned int) +COMMON_JSON_GET(long) +COMMON_JSON_GET(unsigned long) +COMMON_JSON_GET(long long) +COMMON_JSON_GET(unsigned long long) +COMMON_JSON_GET(float) +COMMON_JSON_GET(double) +COMMON_JSON_GET(std::string) +COMMON_JSON_GET(std::vector) + +#undef COMMON_JSON_GET diff --git a/common/json.h b/common/json.h new file mode 100644 index 0000000000..ab4163790e --- /dev/null +++ b/common/json.h @@ -0,0 +1,234 @@ +#pragma once + +// JSON object, it works without the need to include a JSON library header +// note: object keys keep the order in which they are added + +#include +#include +#include +#include +#include +#include +#include +#include + +class common_json; +class common_json_ref; + +// one value of the backing library, only json.cpp knows what it is +struct common_json_node; + +struct common_json_node_deleter { + void operator()(common_json_node * node) const; +}; + +struct common_json_error : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +// one value, tagged so that this header stays free of the backing library +struct common_json_value { + enum value_type { + VAL_NULL, + VAL_BOOL, + VAL_INT, + VAL_UINT, + VAL_DOUBLE, + VAL_STRING, + VAL_JSON, + }; + + value_type type = VAL_NULL; + + union { + bool val_bool; + int64_t val_int; + uint64_t val_uint = 0; + double val_double; + }; + + std::string val_string; + std::shared_ptr val_json; + + common_json_value(std::nullptr_t = nullptr) : type(VAL_NULL) {} + common_json_value(bool val) : type(VAL_BOOL), val_bool(val) {} + common_json_value(std::string val) : type(VAL_STRING), val_string(std::move(val)) {} + common_json_value(const char * val); + common_json_value(const common_json & val); + common_json_value(const common_json_ref & val); + + template ::value && !std::is_same::value, int>::type = 0> + common_json_value(T val) : type(std::is_signed::value ? VAL_INT : VAL_UINT) { + if (std::is_signed::value) { + val_int = (int64_t) val; + } else { + val_uint = (uint64_t) val; + } + } + + template ::value, int>::type = 0> + common_json_value(T val) : type(VAL_DOUBLE), val_double((double) val) {} +}; + +struct common_json_item { + std::string key; + common_json_value val; + + template + common_json_item(std::string key, T && val) : + key(std::move(key)), val(std::forward(val)) {} +}; + +// view to a value owned by a common_json, it goes stale if the owner gets a new key +class common_json_ref { + public: + explicit common_json_ref(common_json_node * node) : node(node) {} + + common_json_ref(const common_json_ref &) = default; + + // rebinding a view is almost always a write-through by mistake, use assign() to write + common_json_ref & operator=(const common_json_ref &) = delete; + + bool is_null() const; + bool is_object() const; + bool is_array() const; + bool is_string() const; + bool is_boolean() const; + bool is_number() const; + bool is_number_integer() const; + + bool empty() const; + size_t size() const; + + bool contains(const std::string & key) const; + + bool operator==(const common_json_value & val) const; + bool operator!=(const common_json_value & val) const; + + // at() throws if the key is missing, operator[] adds a null value instead + common_json_ref at(const std::string & key) const; + common_json_ref operator[](const std::string & key) const; + common_json_ref operator[](size_t idx) const; + + // only for the types instantiated in json.cpp, the rest fails at link time + template T get() const; + + template + T value(const std::string & key, T def) const { + return contains(key) ? at(key).get() : def; + } + + std::string value(const std::string & key, const char * def) const { + return contains(key) ? at(key).get() : std::string(def); + } + + void assign(const common_json_value & val); + void set(const common_json_item & item); + void push_back(const common_json_value & val); + + template + common_json_ref & operator=(T && val) { + assign(common_json_value(std::forward(val))); + return *this; + } + + std::string dump(int indent = -1) const; + + // walks an array by index, or an object in insertion order + class iterator { + public: + iterator(common_json_node * node, size_t idx) : node(node), idx(idx) {} + + common_json_ref operator*() const; + std::string key() const; + + iterator & operator++() { + idx++; + return *this; + } + + bool operator!=(const iterator & other) const { return idx != other.idx; } + bool operator==(const iterator & other) const { return idx == other.idx; } + + private: + common_json_node * node; + size_t idx; + }; + + iterator begin() const { return iterator(node, 0); } + iterator end() const { return iterator(node, size()); } + + // allows: for (const auto & [key, val] : obj.items()) + class items_view { + public: + items_view(common_json_node * node, size_t n) : node(node), n(n) {} + + class iterator { + public: + iterator(common_json_node * node, size_t idx) : node(node), idx(idx) {} + + std::pair operator*() const; + + iterator & operator++() { + idx++; + return *this; + } + + bool operator!=(const iterator & other) const { return idx != other.idx; } + + private: + common_json_node * node; + size_t idx; + }; + + iterator begin() const { return iterator(node, 0); } + iterator end() const { return iterator(node, n); } + + private: + common_json_node * node; + size_t n; + }; + + items_view items() const { return items_view(node, size()); } + + common_json_node * get_node() const { return node; } + + protected: + common_json_node * node; +}; + +// owns the value it points to +class common_json : public common_json_ref { + public: + common_json(); + common_json(std::initializer_list items); + common_json(const common_json & other); + common_json(common_json && other) noexcept; + + common_json & operator=(const common_json & other); + common_json & operator=(common_json && other) noexcept; + + // out-of-line, the deleter needs to know the real type + ~common_json(); + + // throws common_json_error if the text is not valid JSON + static common_json parse(const std::string & text); + + static common_json array(); + static common_json object(); + + // holds a single value, e.g. make("abc").dump() gives "\"abc\"" + static common_json make(const common_json_value & val); + + private: + std::unique_ptr pimpl; +}; + +// bridge for code that still uses internal component from nlohmann::json +// usage: common_json_raw(j) +// TODO: maybe completely remove this in the future + +template T & common_json_raw(common_json_ref & json); +template const T & common_json_raw(const common_json_ref & json); + +template common_json common_json_from_raw(const T & json);