summaryrefslogtreecommitdiffstats
path: root/include/cepis/json.hpp
blob: 38bde79fbe4d4a8abb0a4a6d835955d03fcb5bff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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