NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
Loading...
Searching...
No Matches
json.h
Go to the documentation of this file.
1
21#pragma once
22
23#include <neograph/api.h>
24
25#include <yyjson.h>
26
27#include <cstddef>
28#include <cstdint>
29#include <initializer_list>
30#include <iosfwd>
31#include <ostream>
32#include <map>
33#include <memory>
34#include <stdexcept>
35#include <string>
36#include <string_view>
37#include <type_traits>
38#include <utility>
39#include <vector>
40
41namespace neograph {
42
43namespace detail {
44class JsonBuilder;
45}
46
47class NEOGRAPH_API json {
48public:
49 // ----- Exceptions -----
50 class exception : public std::runtime_error {
51 public:
52 using std::runtime_error::runtime_error;
53 };
54 class parse_error : public exception {
55 public:
56 using exception::exception;
57 };
58 class type_error : public exception {
59 public:
60 using exception::exception;
61 };
62 class out_of_range : public exception {
63 public:
64 using exception::exception;
65 };
66
67 // ----- Construction -----
68 json(); // null
69 json(std::nullptr_t);
70 json(bool b);
71 json(int i);
72 json(unsigned i);
73 json(long i);
74 json(unsigned long i);
75 json(long long i);
76 json(unsigned long long i);
77 json(double d);
78 json(float f);
79 json(const char* s);
80 json(const std::string& s);
81 json(std::string_view s);
82 json(const std::vector<std::string>& v);
83
84 json(std::initializer_list<json> il);
85
86 // ----- Copy / move -----
87 json(const json& other);
88 json(json&& other) noexcept;
89 json& operator=(const json& other) &;
90 json& operator=(json&& other) & noexcept;
91
92 // rvalue assignment: write-through to parent if this handle came from operator[]
93 json& operator=(const json& other) &&;
94 json& operator=(json&& other) && noexcept;
95
96 ~json();
97
98 // ----- Factories -----
99 static json object();
100 static json array();
101 static json array(std::initializer_list<json> il);
102
103 // parse() refuses text that nests arrays and objects deeper than this.
104 // Copying and comparing a value recurse once per level, as do some of
105 // NeoGraph's own walks over values. At this depth they all fit the 512 KiB
106 // stack of a macOS secondary thread.
107 static constexpr std::size_t max_parse_depth = 256;
108
109 // Throws parse_error for text that is not JSON or that nests arrays and
110 // objects deeper than max_parse_depth.
111 static json parse(std::string_view s);
112 static json parse(std::istream& in);
113
114 // The same with a limit of max_depth levels; 0 admits only scalars. A
115 // limit above max_parse_depth is for text the caller trusts, on a thread
116 // whose stack can copy and compare a value that deep.
117 static json parse(std::string_view s, std::size_t max_depth);
118 static json parse(std::istream& in, std::size_t max_depth);
119
120 // ----- Type queries -----
121 bool is_null() const noexcept;
122 bool is_boolean() const noexcept;
123 bool is_bool() const noexcept { return is_boolean(); }
124 bool is_number() const noexcept;
125 bool is_number_integer() const noexcept;
126 bool is_number_unsigned() const noexcept;
127 bool is_number_float() const noexcept;
128 bool is_string() const noexcept;
129 bool is_array() const noexcept;
130 bool is_object() const noexcept;
131 bool is_primitive() const noexcept; // not null and not container
132
133 // ----- Size -----
134 size_t size() const noexcept;
135 bool empty() const noexcept;
136
137 // ----- Access (mutable — get-or-create) -----
138 json operator[](const char* key);
139 json operator[](const std::string& key);
140 json operator[](size_t idx);
141 json operator[](int idx) { return (*this)[static_cast<size_t>(idx)]; }
142
143 // ----- Access (const — read-only) -----
144 json operator[](const char* key) const;
145 json operator[](const std::string& key) const;
146 json operator[](size_t idx) const;
147 json operator[](int idx) const { return (*this)[static_cast<size_t>(idx)]; }
148
149 // ----- at (throws on missing) -----
150 json at(const char* key);
151 json at(const std::string& key);
152 json at(size_t idx);
153 json at(int idx) { return at(static_cast<size_t>(idx)); }
154 json at(const char* key) const;
155 json at(const std::string& key) const;
156 json at(size_t idx) const;
157 json at(int idx) const { return at(static_cast<size_t>(idx)); }
158
159 // ----- Array endpoints (nlohmann-compat shorthand for arr[0] / arr[size-1]) -----
160 // Throws std::out_of_range if !is_array() or empty(); matches at()'s contract.
161 json front();
162 json back();
163 json front() const;
164 json back() const;
165
166 // ----- Membership -----
167 bool contains(const char* key) const;
168 bool contains(const std::string& key) const;
169
170 // ----- Extraction -----
171 template <typename T> T get() const;
172
173 // ----- Safe get with default (object-scoped) -----
174 template <typename T> T value(const std::string& key, const T& default_val) const;
175 std::string value(const std::string& key, const char* default_val) const;
176
177 // ----- Mutation -----
178 void push_back(const json& v);
179 void push_back(json&& v);
180
181 // ----- Serialization -----
182 std::string dump(int indent = -1) const;
183
184 // ----- Equality -----
185 bool operator==(const json& other) const;
186 bool operator!=(const json& other) const { return !(*this == other); }
187
188 // ----- Iteration -----
189 class NEOGRAPH_API iterator {
190 public:
191 iterator() = default;
192 json operator*() const;
193 iterator& operator++();
194 bool operator!=(const iterator& o) const { return !(*this == o); }
195 bool operator==(const iterator& o) const {
196 // Two iterators at end() compare equal regardless of idx_.
197 if (done_ && o.done_) return parent_ == o.parent_;
198 return parent_ == o.parent_ && idx_ == o.idx_ && done_ == o.done_;
199 }
200
201 // For .items() structured binding support
202 std::string key() const;
203 json value() const;
204
205 private:
206 friend class json;
207 std::shared_ptr<yyjson_mut_doc> doc_;
208 yyjson_mut_val* parent_ = nullptr;
209 std::vector<yyjson_mut_val*> keys_; // populated for objects
210 std::vector<yyjson_mut_val*> vals_; // populated for both
211 size_t idx_ = 0;
212 bool is_object_ = false;
213 bool done_ = true;
214 };
215
216 iterator begin() const;
217 iterator end() const;
218
219 // find (for .find(key) != .end() idiom)
220 iterator find(const std::string& key) const;
221 iterator find(const char* key) const;
222
223 // items() for structured binding iteration — defined after class body.
224 class items_proxy;
225 items_proxy items() const;
226
227 // ----- Internal helpers (public for ADL callers) -----
228 yyjson_mut_doc* raw_doc() const { return doc_.get(); }
229 yyjson_mut_val* raw_val() const { return val_; }
230
231private:
232 // Doc shared via custom deleter; val_ points into doc_.
233 std::shared_ptr<yyjson_mut_doc> doc_;
234 yyjson_mut_val* val_ = nullptr;
235
236 // Write-through target (set only for handles returned by operator[]).
237 // When operator= is called on an rvalue with a parent set, the assignment
238 // replaces the corresponding key/index in the parent container.
239 yyjson_mut_val* parent_ = nullptr;
240 bool parent_is_array_ = false;
241 std::string parent_key_;
242 size_t parent_idx_ = 0;
243
244 // ----- Private constructors -----
245 json(std::shared_ptr<yyjson_mut_doc> doc, yyjson_mut_val* val);
246
247 // Private ctor used by operator[] to create a "reference" handle.
248 struct ref_tag {};
249 json(ref_tag,
250 std::shared_ptr<yyjson_mut_doc> doc,
251 yyjson_mut_val* val,
252 yyjson_mut_val* parent,
253 std::string key);
254 json(ref_tag,
255 std::shared_ptr<yyjson_mut_doc> doc,
256 yyjson_mut_val* val,
257 yyjson_mut_val* parent,
258 size_t idx);
259
260 // Helper: ensure this handle has its own doc and root val.
261 void ensure_own_doc();
262
263 // Helper: write a value into parent at the stored key/idx.
264 // src_val is a mut_val owned by some doc; we deep-copy into parent's doc.
265 void write_through(yyjson_mut_val* src_val, yyjson_mut_doc* src_doc);
266
267 // Helper: take a plain mut_val and install it as this json's own root.
268 void reset_to(yyjson_mut_val* v);
269
270 // Factory helpers
271 static std::shared_ptr<yyjson_mut_doc> make_doc();
272
273 friend void to_json_forwarding(json& j, const json& v);
274 // Builds values in one document and hands it over (#478).
275 friend class detail::JsonBuilder;
276};
277
278// ----- items() proxy (defined outside json so it can hold a json member) -----
279class NEOGRAPH_API json::items_proxy {
280public:
281 explicit items_proxy(const json& j) : j_(j) {}
282 class NEOGRAPH_API iterator {
283 public:
284 iterator() = default;
285 std::pair<std::string, json> operator*() const {
286 return {it_.key(), it_.value()};
287 }
288 iterator& operator++() { ++it_; return *this; }
289 bool operator!=(const iterator& o) const { return it_ != o.it_; }
290 bool operator==(const iterator& o) const { return it_ == o.it_; }
291 private:
292 friend class items_proxy;
293 json::iterator it_;
294 };
295 iterator begin() const { iterator it; it.it_ = j_.begin(); return it; }
296 iterator end() const { iterator it; it.it_ = j_.end(); return it; }
297private:
298 json j_;
299};
300
301inline json::items_proxy json::items() const { return items_proxy{*this}; }
302
303// ----- ADL-style helpers used in types.h — extensible via argument-dependent lookup -----
304inline void to_json(json& j, const json& v) { j = v; }
305inline void from_json(const json& j, json& v) { v = j; }
306
307// ----- Stream output (via dump()) -----
308inline std::ostream& operator<<(std::ostream& os, const json& j) {
309 return os << j.dump();
310}
311
312// ===========================================================================
313// Explicit specializations declared here so template `value<T>()` sees them.
314//
315// NEOGRAPH_API on each one is required for MSVC: explicit specializations
316// declared at namespace scope (outside the class body) don't inherit the
317// class's dllexport. The implementations live in src/core/json.cpp;
318// without per-specialization annotation, neograph_llm and other downstream
319// libraries hit `LNK2019: unresolved external symbol json::get<T>` even
320// though the symbols are present in neograph_core.dll.
321// ===========================================================================
322
323template <> NEOGRAPH_API json json::get<json>() const;
324template <> NEOGRAPH_API std::string json::get<std::string>() const;
325// Borrowed UTF-8 bytes; valid while the owning document lives and is not mutated.
326template <> NEOGRAPH_API std::string_view json::get<std::string_view>() const;
327template <> NEOGRAPH_API bool json::get<bool>() const;
328template <> NEOGRAPH_API int json::get<int>() const;
329template <> NEOGRAPH_API unsigned json::get<unsigned>() const;
330template <> NEOGRAPH_API long json::get<long>() const;
331template <> NEOGRAPH_API unsigned long json::get<unsigned long>() const;
332template <> NEOGRAPH_API long long json::get<long long>() const;
333template <> NEOGRAPH_API unsigned long long json::get<unsigned long long>() const;
334template <> NEOGRAPH_API double json::get<double>() const;
335template <> NEOGRAPH_API float json::get<float>() const;
336template <> NEOGRAPH_API std::vector<std::string> json::get<std::vector<std::string>>() const;
337
338template <typename T>
339T json::value(const std::string& key, const T& default_val) const {
340 if (!is_object()) return default_val;
341 auto child = (*this)[key];
342 if (child.is_null()) return default_val;
343 try {
344 return child.get<T>();
345 } catch (...) {
346 return default_val;
347 }
348}
349
350inline std::string json::value(const std::string& key, const char* default_val) const {
351 return value<std::string>(key, std::string(default_val));
352}
353
354} // namespace neograph
NEOGRAPH_API export/import macro for shared-library builds.