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
|