#ifndef CEPIS_JSON_HPP #define CEPIS_JSON_HPP #include #include #include #include #include #include #include namespace cepis { /// An immutable-by-convention JSON value, sufficient for this API's responses. /// /// The library ships its own parser rather than depending on a general-purpose /// one: the payloads are flat objects of strings and numbers, and a client /// whose only dependency is libcurl is easier to drop into an existing build. /// /// Objects preserve **insertion order** — the API documents a fixed field /// order and round-tripping it through `dump()` keeps that visible. Lookup is /// a linear scan, which is the faster choice at this size anyway. /// /// The parser accepts UTF-8 input, decodes `\uXXXX` escapes (surrogate pairs /// included) into UTF-8, and rejects trailing garbage after the top-level /// value. class Json { public: /// The six JSON types. enum class Kind : std::uint8_t { Null, Bool, Number, String, Array, Object }; using Array = std::vector; using Object = std::vector>; /// Constructs a null value. Json() = default; /// Constructs a value of the given kind, empty for the composite kinds. static Json boolean(bool value); static Json number(double value); static Json string(std::string value); static Json array(Array value = {}); static Json object(Object value = {}); /// Parses `text`, returning `std::nullopt` if it is not one well-formed /// JSON value. Callers in this library turn that into `InvalidResponse`. static std::optional parse(std::string_view text); /// Serialises back to compact JSON. Non-ASCII is emitted as raw UTF-8. [[nodiscard]] std::string dump() const; [[nodiscard]] Kind kind() const noexcept { return kind_; } [[nodiscard]] bool is_null() const noexcept { return kind_ == Kind::Null; } [[nodiscard]] bool is_bool() const noexcept { return kind_ == Kind::Bool; } [[nodiscard]] bool is_number() const noexcept { return kind_ == Kind::Number; } [[nodiscard]] bool is_string() const noexcept { return kind_ == Kind::String; } [[nodiscard]] bool is_array() const noexcept { return kind_ == Kind::Array; } [[nodiscard]] bool is_object() const noexcept { return kind_ == Kind::Object; } /// Typed accessors. Each returns a harmless default for a mismatched kind /// rather than throwing: every field of this API is optional in practice, /// and a wrong type on one field must not cost the whole response. [[nodiscard]] bool as_bool(bool fallback = false) const noexcept; [[nodiscard]] double as_number(double fallback = 0.0) const noexcept; [[nodiscard]] const std::string& as_string() const noexcept; [[nodiscard]] const Array& as_array() const noexcept; [[nodiscard]] const Object& as_object() const noexcept; /// Returns the member named `key`, or `nullptr` when this is not an object /// or has no such member. [[nodiscard]] const Json* find(std::string_view key) const noexcept; /// Appends a member. Replaces the value if `key` is already present, so the /// original position in the order is kept. void set(std::string key, Json value); /// Appends an element. Does nothing unless this is an array. void push_back(Json value); private: Kind kind_ = Kind::Null; bool bool_ = false; double number_ = 0.0; std::string string_; Array array_; Object object_; }; } // namespace cepis #endif // CEPIS_JSON_HPP