Back to Site
Loading...
Searching...
No Matches
json.h
Go to the documentation of this file.
1#pragma once
2
36#include "librats/util/rats_export.h"
37#include <cstdint>
38#include <initializer_list>
39#include <iosfwd>
40#include <stdexcept>
41#include <string>
42#include <type_traits>
43#include <utility>
44#include <vector>
45
46namespace librats {
47
51class JsonError : public std::runtime_error {
52public:
53 explicit JsonError(const std::string& what) : std::runtime_error(what) {}
54};
55
56class RATS_API Json {
57public:
58 enum class Type : uint8_t {
59 Null,
60 Boolean,
61 Integer, // signed, stored as int64_t
62 Unsigned, // unsigned, stored as uint64_t
63 Float, // stored as double
64 String,
65 Array,
66 Object,
67 Discarded, // result of a non-throwing parse failure
68 };
69
70 using Array = std::vector<Json>;
71
73 class Object {
74 public:
75 using value_type = std::pair<std::string, Json>;
76 using storage = std::vector<value_type>;
77
78 Object() = default;
79 Object(const Object&) = default;
80 Object(Object&&) noexcept = default;
81 Object& operator=(const Object&) = default;
82 Object& operator=(Object&&) noexcept = default;
83
84 Json& operator[](const std::string& key); // inserts null if absent
85 Json& operator[](std::string&& key); // ditto, moves key on insert
86 const Json* find(const std::string& key) const; // nullptr if absent
87 Json* find(const std::string& key); // nullptr if absent
88 bool contains(const std::string& key) const { return find(key) != nullptr; }
89 bool erase(const std::string& key);
90
91 std::size_t size() const { return items_.size(); }
92 bool empty() const { return items_.empty(); }
93 void clear() { items_.clear(); index_ = {}; }
94
95 storage::iterator begin() { return items_.begin(); }
96 storage::iterator end() { return items_.end(); }
97 storage::const_iterator begin() const { return items_.begin(); }
98 storage::const_iterator end() const { return items_.end(); }
99
100 bool operator==(const Object& other) const; // order-independent
101
102 private:
103 // Small objects (the common case — a peer record, a config node) keep
104 // only the insertion-ordered vector and find keys with a linear scan,
105 // which beats hashing for a handful of entries and costs no allocation.
106 // Past the threshold we build an open-addressing index that stores *only*
107 // 1-based positions into items_ (a 0 slot means empty) — so it copies no
108 // keys and is one small vector instead of a node-per-key hash map. items_
109 // stays the single source of truth; every probe reads the key back from
110 // it. The index is non-empty exactly while the object is indexed.
111 static constexpr std::size_t kIndexThreshold = 16;
112
113 static std::size_t hash_key(const std::string& key);
114 void rebuild_index(); // size the table for items_ and fill it from scratch
115 void reindex(); // after erase: rebuild while large, else drop the index
116 bool indexed() const { return !index_.empty(); }
117
118 // Shared find-or-insert for both operator[] overloads. K is deduced as
119 // `const std::string&` or `std::string&&`, so the key is copied or moved
120 // into storage to match how the caller supplied it.
121 template <typename K>
122 Json& emplace_key(K&& key);
123
124 storage items_;
125 // Open-addressing slots: value 0 is an empty slot, otherwise it is
126 // (items_ index + 1). uint32_t positions cap an object at ~4 billion
127 // keys — far beyond any real JSON document.
128 std::vector<std::uint32_t> index_;
129 };
130
131 // ── Construction ────────────────────────────────────────────────────────
132
133 Json() noexcept : type_(Type::Null) {}
134 Json(std::nullptr_t) noexcept : type_(Type::Null) {}
135 Json(bool b) noexcept : type_(Type::Boolean) { bool_ = b; }
136
137 template <typename T,
138 typename std::enable_if<std::is_integral<T>::value &&
139 !std::is_same<T, bool>::value,
140 int>::type = 0>
141 Json(T v) noexcept {
142 if (std::is_signed<T>::value) {
143 type_ = Type::Integer;
144 int_ = static_cast<int64_t>(v);
145 } else {
146 type_ = Type::Unsigned;
147 uint_ = static_cast<uint64_t>(v);
148 }
149 }
150
151 template <typename T,
152 typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
153 Json(T v) noexcept : type_(Type::Float) { float_ = static_cast<double>(v); }
154
155 Json(const char* s) : type_(Type::String) { str_ = new std::string(s ? s : ""); }
156 Json(const std::string& s) : type_(Type::String) { str_ = new std::string(s); }
157 Json(std::string&& s) : type_(Type::String) { str_ = new std::string(std::move(s)); }
158
162 Json(std::initializer_list<Json> init);
163
164 Json(const Json& other) { copy_from(other); }
165 Json(Json&& other) noexcept { move_from(other); }
166
167 Json& operator=(const Json& other);
168 Json& operator=(Json&& other) noexcept;
169
170 ~Json() { destroy(); }
171
173 static Json array() { Json j; j.type_ = Type::Array; j.arr_ = new Array(); return j; }
174 static Json object() { Json j; j.type_ = Type::Object; j.obj_ = new Object(); return j; }
175
176 // ── Parsing ─────────────────────────────────────────────────────────────
177 //
178 // The second argument exists only for nlohmann call-site compatibility
179 // (a parser callback, which this implementation ignores). When
180 // allow_exceptions is false a malformed document yields a Discarded value
181 // (see is_discarded()) instead of throwing.
182
183 static Json parse(const std::string& text, std::nullptr_t = nullptr,
184 bool allow_exceptions = true);
185 static Json parse(const char* text, std::nullptr_t = nullptr,
186 bool allow_exceptions = true);
187
188 template <typename InputIt>
189 static Json parse(InputIt first, InputIt last, std::nullptr_t = nullptr,
190 bool allow_exceptions = true) {
191 // std::string's range constructor copies [first, last), converting each
192 // element to char — this handles const char*, const uint8_t*, etc.
193 std::string buf(first, last);
194 return parse(buf, nullptr, allow_exceptions);
195 }
196
197 // ── Serialisation ───────────────────────────────────────────────────────
198 //
199 // indent < 0 (the default) produces the most compact form. indent >= 0
200 // pretty-prints with that many spaces per level.
201 std::string dump(int indent = -1) const;
202
203 // ── Type inspection ─────────────────────────────────────────────────────
204
205 Type type() const noexcept { return type_; }
206 bool is_null() const noexcept { return type_ == Type::Null; }
207 bool is_boolean() const noexcept { return type_ == Type::Boolean; }
208 bool is_number() const noexcept {
209 return type_ == Type::Integer || type_ == Type::Unsigned || type_ == Type::Float;
210 }
211 bool is_number_integer() const noexcept {
212 return type_ == Type::Integer || type_ == Type::Unsigned;
213 }
214 bool is_number_unsigned() const noexcept { return type_ == Type::Unsigned; }
215 bool is_number_float() const noexcept { return type_ == Type::Float; }
216 bool is_string() const noexcept { return type_ == Type::String; }
217 bool is_array() const noexcept { return type_ == Type::Array; }
218 bool is_object() const noexcept { return type_ == Type::Object; }
219 bool is_discarded() const noexcept { return type_ == Type::Discarded; }
220 bool is_primitive() const noexcept {
221 return is_null() || is_boolean() || is_number() || is_string();
222 }
223 bool is_structured() const noexcept { return is_array() || is_object(); }
224
226 std::size_t size() const noexcept;
228 bool empty() const noexcept;
229
230 // ── Object / array access ───────────────────────────────────────────────
231
232 Json& operator[](const std::string& key);
233 Json& operator[](const char* key) { return operator[](std::string(key)); }
234 const Json& operator[](const std::string& key) const;
235 const Json& operator[](const char* key) const { return operator[](std::string(key)); }
236
237 Json& operator[](int index) { return operator[](static_cast<std::size_t>(index)); }
238 Json& operator[](std::size_t index);
239 const Json& operator[](int index) const {
240 return operator[](static_cast<std::size_t>(index));
241 }
242 const Json& operator[](std::size_t index) const;
243
245 Json& at(const std::string& key);
246 const Json& at(const std::string& key) const;
247 Json& at(std::size_t index);
248 const Json& at(std::size_t index) const;
249
250 bool contains(const std::string& key) const {
251 return is_object() && obj_->contains(key);
252 }
253 bool erase(const std::string& key);
254 void erase(std::size_t index);
255
257 const Json& front() const;
259 const Json& back() const;
260
261 void push_back(const Json& value);
262 void push_back(Json&& value);
263 template <typename... Args>
264 Json& emplace_back(Args&&... args) {
265 push_back(Json(std::forward<Args>(args)...));
266 return back();
267 }
268
269 void clear();
270
271 // ── Typed extraction ────────────────────────────────────────────────────
272
273 template <typename T>
274 T get() const {
275 if constexpr (std::is_same<T, bool>::value) {
276 return static_cast<T>(as_bool());
277 } else if constexpr (std::is_integral<T>::value) {
278 if constexpr (std::is_unsigned<T>::value) return static_cast<T>(as_uint64());
279 else return static_cast<T>(as_int64());
280 } else if constexpr (std::is_floating_point<T>::value) {
281 return static_cast<T>(as_double());
282 } else {
283 return get_impl(static_cast<T*>(nullptr));
284 }
285 }
286
289 template <typename T,
290 typename std::enable_if<(std::is_arithmetic<T>::value ||
291 std::is_same<T, std::string>::value) &&
292 !std::is_same<T, Json>::value,
293 int>::type = 0>
294 operator T() const { return get<T>(); }
295
298 template <typename T>
299 T value(const std::string& key, const T& default_value) const {
300 if (is_object()) {
301 if (const Json* v = obj_->find(key)) return v->get<T>();
302 }
303 return default_value;
304 }
306 std::string value(const std::string& key, const char* default_value) const {
307 if (is_object()) {
308 if (const Json* v = obj_->find(key)) return v->get<std::string>();
309 }
310 return std::string(default_value);
311 }
312
313 // ── Iteration ───────────────────────────────────────────────────────────
314 //
315 // Range-for visits array elements, or object values in insertion order.
316 // The iterator exposes .key()/.value() (nlohmann-style). For key/value
317 // structured bindings, use items(): `for (auto e : j.items()) ...`.
318
319 template <bool Const>
320 class Iterator {
321 public:
322 using JsonRef = typename std::conditional<Const, const Json&, Json&>::type;
323 using JsonPtr = typename std::conditional<Const, const Json*, Json*>::type;
324
325 Iterator(JsonPtr owner, std::size_t idx) : owner_(owner), idx_(idx) {}
326
327 JsonRef operator*() const { return value(); }
328 JsonPtr operator->() const { return &value(); }
329 Iterator& operator++() { ++idx_; return *this; }
330 Iterator operator++(int) { Iterator tmp = *this; ++idx_; return tmp; }
331 bool operator==(const Iterator& o) const { return owner_ == o.owner_ && idx_ == o.idx_; }
332 bool operator!=(const Iterator& o) const { return !(*this == o); }
333
334 const std::string& key() const;
335 JsonRef value() const;
336
337 private:
338 JsonPtr owner_;
339 std::size_t idx_;
340 };
341
344
345 iterator begin() { return iterator(this, 0); }
346 iterator end() { return iterator(this, size()); }
347 const_iterator begin() const { return const_iterator(this, 0); }
348 const_iterator end() const { return const_iterator(this, size()); }
349 const_iterator cbegin() const { return const_iterator(this, 0); }
350 const_iterator cend() const { return const_iterator(this, size()); }
351
355 template <bool Const>
357 public:
358 using JsonPtr = typename std::conditional<Const, const Json*, Json*>::type;
359 explicit ItemsProxy(JsonPtr owner) : owner_(owner) {}
360 Iterator<Const> begin() const { return Iterator<Const>(owner_, 0); }
361 Iterator<Const> end() const { return Iterator<Const>(owner_, owner_->size()); }
362 private:
363 JsonPtr owner_;
364 };
365
367 ItemsProxy<true> items() const { return ItemsProxy<true>(this); }
368
369 // ── Equality ────────────────────────────────────────────────────────────
370
371 bool operator==(const Json& other) const;
372 bool operator!=(const Json& other) const { return !(*this == other); }
373
374 // ── Direct container access (advanced) ──────────────────────────────────
375
377 const Array& as_array() const;
379 const Object& as_object() const;
380
381private:
382 // value storage: scalars live inline, heavy payloads behind a pointer.
383 Type type_ = Type::Null;
384 union {
385 bool bool_;
386 int64_t int_;
387 uint64_t uint_;
388 double float_;
389 std::string* str_;
392 };
393
394 void destroy() noexcept;
395 void copy_from(const Json& other);
396 void move_from(Json& other) noexcept;
397
398 bool as_bool() const;
399 int64_t as_int64() const;
400 uint64_t as_uint64() const;
401 double as_double() const;
402 const std::string& as_string() const;
403
404 // get_impl tag overloads — only std::string is supported as a class type.
405 std::string get_impl(std::string*) const { return as_string(); }
406
407 void dump_to(std::string& out, int indent, int depth) const;
408
409 static Json make_discarded() { Json j; j.type_ = Type::Discarded; return j; }
410
411 // The parser is implemented in json.cpp.
412 friend class JsonParser;
413};
414
415// Stream helpers: `is >> j` parses the whole stream; `os << j` writes dump().
416std::istream& operator>>(std::istream& is, Json& j);
417std::ostream& operator<<(std::ostream& os, const Json& j);
418
419// ── Iterator member definitions (need the complete Json type) ───────────────
420
421template <bool Const>
422inline const std::string& Json::Iterator<Const>::key() const {
423 if (owner_->type_ == Type::Object) {
424 return (owner_->obj_->begin() + idx_)->first;
425 }
426 // Arrays: synthesise a decimal index string on demand (thread-local cache).
427 static thread_local std::string idx_str;
428 idx_str = std::to_string(idx_);
429 return idx_str;
430}
431
432template <bool Const>
434 if (owner_->type_ == Type::Object) {
435 return (owner_->obj_->begin() + idx_)->second;
436 }
437 if (owner_->type_ == Type::Array) {
438 return (*owner_->arr_)[idx_];
439 }
440 // Scalars iterate as a single element.
441 return *owner_;
442}
443
444} // namespace librats
Thrown by the throwing parse path and by type-mismatched accessors.
Definition json.h:51
JsonError(const std::string &what)
Definition json.h:53
A key/value view supporting structured bindings: for (auto&& [key, val] : obj.items()) { ....
Definition json.h:356
Iterator< Const > end() const
Definition json.h:361
Iterator< Const > begin() const
Definition json.h:360
ItemsProxy(JsonPtr owner)
Definition json.h:359
typename std::conditional< Const, const Json *, Json * >::type JsonPtr
Definition json.h:358
JsonRef operator*() const
Definition json.h:327
typename std::conditional< Const, const Json &, Json & >::type JsonRef
Definition json.h:322
JsonPtr operator->() const
Definition json.h:328
typename std::conditional< Const, const Json *, Json * >::type JsonPtr
Definition json.h:323
const std::string & key() const
Definition json.h:422
bool operator==(const Iterator &o) const
Definition json.h:331
Iterator(JsonPtr owner, std::size_t idx)
Definition json.h:325
Iterator operator++(int)
Definition json.h:330
Iterator & operator++()
Definition json.h:329
JsonRef value() const
Definition json.h:433
bool operator!=(const Iterator &o) const
Definition json.h:332
Insertion-ordered string->Json map with O(1) average lookup.
Definition json.h:73
Object(const Object &)=default
storage::const_iterator end() const
Definition json.h:98
storage::iterator begin()
Definition json.h:95
Object(Object &&) noexcept=default
std::pair< std::string, Json > value_type
Definition json.h:75
bool erase(const std::string &key)
bool operator==(const Object &other) const
std::size_t size() const
Definition json.h:91
std::vector< value_type > storage
Definition json.h:76
bool empty() const
Definition json.h:92
storage::iterator end()
Definition json.h:96
storage::const_iterator begin() const
Definition json.h:97
const Json & operator[](const std::string &key) const
const Json & operator[](std::size_t index) const
Json & at(const std::string &key)
Bounds/existence-checked access; throws JsonError when missing.
bool is_primitive() const noexcept
Definition json.h:220
bool is_null() const noexcept
Definition json.h:206
Json & operator=(Json &&other) noexcept
bool is_number_float() const noexcept
Definition json.h:215
static Json parse(InputIt first, InputIt last, std::nullptr_t=nullptr, bool allow_exceptions=true)
Definition json.h:189
bool bool_
Definition json.h:385
bool is_discarded() const noexcept
Definition json.h:219
Json & operator=(const Json &other)
std::string * str_
Definition json.h:389
static Json array()
Explicit empties (also handy to force kind on an otherwise-null value).
Definition json.h:173
Json(const std::string &s)
Definition json.h:156
Array & as_array()
Json(const Json &other)
Definition json.h:164
const Json & at(std::size_t index) const
Array * arr_
Definition json.h:390
Type type() const noexcept
Definition json.h:205
const Json & operator[](int index) const
Definition json.h:239
void push_back(const Json &value)
Json(T v) noexcept
Definition json.h:141
const_iterator end() const
Definition json.h:348
Json(std::initializer_list< Json > init)
nlohmann-style brace initialisation.
int64_t int_
Definition json.h:386
Json & operator[](const char *key)
Definition json.h:233
Json(bool b) noexcept
Definition json.h:135
Json & at(std::size_t index)
Object & as_object()
bool operator!=(const Json &other) const
Definition json.h:372
std::string value(const std::string &key, const char *default_value) const
const char* default resolves to a std::string result (nlohmann parity).
Definition json.h:306
Json & back()
Object * obj_
Definition json.h:391
bool is_number_integer() const noexcept
Definition json.h:211
std::string dump(int indent=-1) const
bool erase(const std::string &key)
iterator end()
Definition json.h:346
std::vector< Json > Array
Definition json.h:70
bool contains(const std::string &key) const
Definition json.h:250
const_iterator cend() const
Definition json.h:350
std::size_t size() const noexcept
Number of elements (array/object), or 0 for null, 1 for any scalar.
Json(const char *s)
Definition json.h:155
const_iterator begin() const
Definition json.h:347
bool is_string() const noexcept
Definition json.h:216
void erase(std::size_t index)
Json(Json &&other) noexcept
Definition json.h:165
double float_
Definition json.h:388
ItemsProxy< true > items() const
Definition json.h:367
Json() noexcept
Definition json.h:133
static Json parse(const char *text, std::nullptr_t=nullptr, bool allow_exceptions=true)
const Json & back() const
const Json & at(const std::string &key) const
bool is_number_unsigned() const noexcept
Definition json.h:214
const Object & as_object() const
Json & front()
bool is_object() const noexcept
Definition json.h:218
const Array & as_array() const
bool is_number() const noexcept
Definition json.h:208
T get() const
Definition json.h:274
void push_back(Json &&value)
T value(const std::string &key, const T &default_value) const
value(key, default): typed object lookup with a fallback.
Definition json.h:299
Json & operator[](int index)
Definition json.h:237
Json & operator[](std::size_t index)
Json(std::string &&s)
Definition json.h:157
bool is_structured() const noexcept
Definition json.h:223
Json(std::nullptr_t) noexcept
Definition json.h:134
ItemsProxy< false > items()
Definition json.h:366
const Json & operator[](const char *key) const
Definition json.h:235
bool is_boolean() const noexcept
Definition json.h:207
uint64_t uint_
Definition json.h:387
bool operator==(const Json &other) const
static Json object()
Definition json.h:174
static Json parse(const std::string &text, std::nullptr_t=nullptr, bool allow_exceptions=true)
const Json & front() const
bool is_array() const noexcept
Definition json.h:217
const_iterator cbegin() const
Definition json.h:349
iterator begin()
Definition json.h:345
Json & emplace_back(Args &&... args)
Definition json.h:264
Definition node.h:73
std::istream & operator>>(std::istream &is, Json &j)
std::ostream & operator<<(std::ostream &os, const Json &j)
STL namespace.