diff options
Diffstat (limited to 'include/cepis/json.hpp')
| -rw-r--r-- | include/cepis/json.hpp | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/include/cepis/json.hpp b/include/cepis/json.hpp new file mode 100644 index 0000000..38bde79 --- /dev/null +++ b/include/cepis/json.hpp @@ -0,0 +1,91 @@ +#ifndef CEPIS_JSON_HPP +#define CEPIS_JSON_HPP + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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<Json>; + using Object = std::vector<std::pair<std::string, Json>>; + + /// 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<Json> 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 |