File: generic-json.md

package info (click to toggle)
glaze 6.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,948 kB
  • sloc: cpp: 121,839; sh: 99; ansic: 26; makefile: 13
file content (298 lines) | stat: -rw-r--r-- 10,909 bytes parent folder | download
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# Generic JSON

While Glaze is focused on strongly typed data, there is basic support for completely generic JSON.

If nothing is known about the JSON structure, then [glz::generic](https://github.com/stephenberry/glaze/blob/main/include/glaze/json/generic.hpp) may be helpful, but it comes at a performance cost due to the use of dynamic memory allocations. The previous `glz::json_t` name remains as an alias for backwards compatibility.

## Generic Type Variants

Glaze provides three generic JSON types with different number storage strategies:

| Type | Number Storage | Use Case |
|------|---------------|----------|
| `glz::generic` | `double` | Fast, JavaScript-compatible (default) |
| `glz::generic_i64` | `int64_t` then `double` | Signed integer precision |
| `glz::generic_u64` | `uint64_t` then `int64_t` then `double` | Full integer range |

### glz::generic (Default)

All numbers are stored as `double`. This is the fastest option and matches JavaScript's number semantics. However, integers larger than 2^53 will lose precision.

```c++
glz::generic json{};
std::string buffer = R"([5,"Hello World",{"pi":3.14}])";
glz::read_json(json, buffer);
assert(json[0].get<double>() == 5.0);
assert(json[1].get<std::string>() == "Hello World");
assert(json[2]["pi"].get<double>() == 3.14);
assert(json[2]["pi"].as<int>() == 3);
```

### glz::generic_i64

Integers are stored as `int64_t`, with fallback to `double` for floating-point numbers. This preserves precision for signed integers up to 9.2 quintillion (2^63-1).

```c++
glz::generic_i64 json{};
std::string buffer = R"({"id": 9007199254740993, "value": 3.14})";
glz::read_json(json, buffer);

// Large integer preserved exactly (would lose precision in double)
assert(json["id"].is_int64());
assert(json["id"].get<int64_t>() == 9007199254740993LL);

// Floating-point stored as double
assert(json["value"].is_double());
assert(json["value"].get<double>() == 3.14);

// Use as<T>() for conversions
assert(json["id"].as<double>() == 9007199254740993.0);
```

### glz::generic_u64

Provides full integer range support. Positive integers are stored as `uint64_t`, negative integers as `int64_t`, and floating-point numbers as `double`. This handles the complete unsigned 64-bit range (0 to 18.4 quintillion).

```c++
glz::generic_u64 json{};
std::string buffer = R"({"big_id": 18446744073709551615, "neg": -100, "pi": 3.14})";
glz::read_json(json, buffer);

// Maximum uint64_t value preserved exactly
assert(json["big_id"].is_uint64());
assert(json["big_id"].get<uint64_t>() == 18446744073709551615ULL);

// Negative integers stored as int64_t
assert(json["neg"].is_int64());
assert(json["neg"].get<int64_t>() == -100);

// Floating-point stored as double
assert(json["pi"].is_double());
assert(json["pi"].get<double>() == 3.14);
```

### Choosing the Right Type

- **`glz::generic`**: Use when performance matters most and you don't need large integer precision, or when interoperating with JavaScript.
- **`glz::generic_i64`**: Use when dealing with signed 64-bit IDs, timestamps, or APIs that return large signed integers.
- **`glz::generic_u64`**: Use when handling database IDs, blockchain values, or any unsigned 64-bit integers.

## Basic Usage

```c++
glz::generic json{};
std::string buffer = R"([5,"Hello World",{"pi":3.14}])";
glz::read_json(json, buffer);
assert(json[0].get<double>() == 5.0);
assert(json[1].get<std::string>() == "Hello World");
assert(json[2]["pi"].get<double>() == 3.14);
assert(json[2]["pi"].as<int>() == 3);
```

```c++
glz::generic json = {
   {"pi", 3.141},
   {"happy", true},
   {"name", "Stephen"},
   {"nothing", nullptr},
   {"answer", {{"everything", 42.0}}},
   {"list", {1.0, 0.0, 2.0}},
   {"object", {
      {"currency", "USD"},
      {"value", 42.99}
   }}
};
std::string buffer{};
glz::write_json(json, buffer);
expect(buffer == R"({"answer":{"everything":42},"happy":true,"list":[1,0,2],"name":"Stephen","object":{"currency":"USD","value":42.99},"pi":3.141})");
```

## get() vs as()

All generic types are variants underneath. The `get<T>()` method mimics a `std::get` call for a variant, which rejects conversions and throws if the type doesn't match. The `as<T>()` method performs conversions.

```c++
glz::generic json{};
glz::read_json(json, "3.14");

json.get<double>();  // 3.14 - exact type match
json.as<int>();      // 3 - converts double to int
```

For `generic_i64` and `generic_u64`, `as<T>()` handles conversions from integer storage:

```c++
glz::generic_i64 json{};
glz::read_json(json, "42");

json.get<int64_t>();  // 42 - stored as int64_t
json.as<double>();    // 42.0 - converts to double
json.as<int>();       // 42 - converts to int
```

## Type Checking generic

All generic types have member functions to check the JSON type:

- `.is_object()`
- `.is_array()`
- `.is_string()`
- `.is_number()` - returns true for any numeric type
- `.is_boolean()`
- `.is_null()`

There are also free functions of these, such as `glz::is_object(...)`

### Additional Type Checks for Integer Modes

`glz::generic_i64` and `glz::generic_u64` provide additional methods:

- `.is_int64()` - true if stored as `int64_t`
- `.is_double()` - true if stored as `double`
- `.is_uint64()` - true if stored as `uint64_t` (only `generic_u64`)
- `.as_number()` - returns the value as `double`, converting if necessary

```c++
glz::generic_i64 json{};
glz::read_json(json, "42");

json.is_number();  // true - it's some kind of number
json.is_int64();   // true - specifically stored as int64_t
json.is_double();  // false - not stored as double

double val = json.as_number();  // 42.0 - converts int64_t to double
```

## .empty()

Calling `.empty()` on a `generic` value will return true if it contains an empty object, array, or string, or a null value. Otherwise, returns false.

## .size()

Calling `.size()` on a `generic` value will return the number of items in an object or array, or the size of a string. Otherwise, returns zero.

## .dump()

Calling `.dump()` on a `generic` value is equivalent to calling `glz::write_json(value)`, which returns an `expected<std::string, glz::error_ctx>`.

## glz::raw_json

There are times when you want to parse JSON into a C++ string, to inspect or decode at a later point. `glz::raw_json` is a simple wrapper around a `std::string` that will decode and encode JSON without needing a concrete structure.

```c++
std::vector<glz::raw_json> v{"0", "1", "2"};
std::string s;
glz::write_json(v, s);
expect(s == R"([0,1,2])");
```

## Using `generic` As The Source

After parsing into a `generic` it is sometimes desirable to parse into a concrete struct or a portion of the `generic` into a struct. Glaze allows a `generic` value to be used as the source where a buffer would normally be passed.

```c++
auto json = glz::read_json<glz::generic>(R"({"foo":"bar"})");
expect(json->contains("foo"));
auto obj = glz::read_json<std::map<std::string, std::string>>(json.value());
// This reads the generic value into a std::map
```

**Performance Note**: When reading primitives or containers (vectors, maps, arrays, etc.) from `generic`, Glaze uses an optimized direct-traversal path that avoids JSON serialization. For complex user-defined structs, it falls back to JSON round-trip to handle reflection metadata.

Another example:

```c++
glz::generic json{};
expect(not glz::read_json(json, R"("Beautiful beginning")"));
std::string v{};
expect(not glz::read<glz::opts{}>(v, json));
expect(v == "Beautiful beginning");
```

### Optimized Conversion from `generic`

When reading from a `generic` into primitives or containers, `glz::read_json` automatically uses an optimized direct-traversal path:

```c++
glz::generic json{};
glz::read_json(json, R"([1, 2, 3, 4, 5])");

// Efficient direct conversion - no JSON serialization overhead
std::vector<int> vec;
auto ec = glz::read_json(vec, json);
if (!ec) {
  // vec now contains {1, 2, 3, 4, 5}
}
```

The optimization automatically applies to:
- **Primitives**: `bool`, `double`, `int`, and other numeric types
- **Strings**: `std::string`
- **Arrays**: `std::vector`, `std::array`, `std::deque`, `std::list`
- **Maps**: `std::map`, `std::unordered_map`
- **Nested combinations**: `std::vector<std::vector<int>>`, `std::map<std::string, std::vector<double>>`, etc.

Benefits of the optimized path:
- **No JSON round-trip**: Converts directly from the internal `generic` representation
- **Memory reuse**: Existing allocations in the target container are preserved
- **Recursive efficiency**: Nested containers are converted with zero intermediate serialization

For complex user-defined structs, `glz::read_json` automatically falls back to JSON round-trip to handle reflection metadata.

**Advanced**: If you need explicit control over the conversion process, `glz::convert_from_generic(result, source)` is available as a lower-level API that returns `error_ctx` instead of using the `expected` wrapper.

## Extracting Containers with JSON Pointers

`glz::get` can be used with JSON Pointers to extract values from a `generic` object. For primitive types (bool, double, std::string), `glz::get` returns a reference wrapper to the value stored in the `generic`:

```c++
glz::generic json{};
std::string buffer = R"({"name": "Alice", "age": 30, "active": true})";
glz::read_json(json, buffer);

// Get primitive types - returns expected<reference_wrapper<T>, error_ctx>
auto name = glz::get<std::string>(json, "/name");
if (name) {
  expect(name->get() == "Alice");
}

auto age = glz::get<double>(json, "/age");
if (age) {
  expect(age->get() == 30.0);
}
```

For container types (vectors, maps, arrays, lists, etc.), `glz::get` deserializes the value and returns a copy:

```c++
glz::generic json{};
std::string buffer = R"({
  "names": ["Alice", "Bob", "Charlie"],
  "scores": {"math": 95, "english": 87}
})";
glz::read_json(json, buffer);

// Get container types - returns expected<T, error_ctx>
auto names = glz::get<std::vector<std::string>>(json, "/names");
if (names) {
  expect(names->size() == 3);
  expect((*names)[0] == "Alice");
}

auto scores = glz::get<std::map<std::string, int>>(json, "/scores");
if (scores) {
  expect(scores->at("math") == 95);
}

// Works with other container types too
auto names_list = glz::get<std::list<std::string>>(json, "/names");
auto names_array = glz::get<std::array<std::string, 3>>(json, "/names");
```

This works because `glz::generic` stores arrays as `std::vector<glz::generic>` and objects as `std::map<std::string, glz::generic>`. When you request a specific container type, Glaze deserializes the generic representation into your desired type.

## See Also

- [JSON Patch (RFC 6902)](./json-patch.md) - Apply structured patches to `glz::generic` documents
- [JSON Merge Patch (RFC 7386)](./json-merge-patch.md) - Apply partial updates to `glz::generic` documents
- [JSON Pointer Syntax](./json-pointer-syntax.md) - Path syntax for navigating JSON documents