Generic JSON
May 16, 2026 ยท View on GitHub
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 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.
Note:
glz::genericpreserves the insertion order of JSON keys. When you parse a JSON object and re-serialize it, the keys will appear in the same order as the original document. If you need lexicographically sorted keys, use the sorted aliases:glz::generic_sorted json{}; glz::generic_sorted_i64 json_i64{}; glz::generic_sorted_u64 json_u64{};
Generic Type Variants
Glaze provides insertion-order and sorted-key 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_sorted | double | Lexicographically sorted object keys |
glz::generic_sorted_i64 | int64_t then double | Sorted keys + signed integer precision |
glz::generic_sorted_u64 | uint64_t then int64_t then double | Sorted keys + 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 will lose precision.
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 (-1).
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).
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.glz::generic_sorted*: Use when you specifically need lexicographically sorted object keys for deterministic output compatibility.
Basic Usage
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 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"({"pi":3.141,"happy":true,"name":"Stephen","nothing":null,"answer":{"everything":42},"list":[1,0,2],"object":{"currency":"USD","value":42.99}})");
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.
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:
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 asint64_t.is_double()- true if stored asdouble.is_uint64()- true if stored asuint64_t(onlygeneric_u64).as_number()- returns the value asdouble, converting if necessary
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.
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.
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:
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:
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
genericrepresentation - 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:
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:
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 glz::ordered_small_map<glz::generic>. When you request a specific container type, Glaze deserializes the generic representation into your desired type.
Compilation time optimization
glaze/json/generic.hpp is a heavyweight header: it pulls in the full JSON read/write machinery so that glz::generic values can be serialized, deserialized, navigated by JSON Pointer, and converted into arbitrary C++ types.
If glz::generic only appears in the signatures of a widely-included header (function parameters, return types, struct members) and the actual reading/writing happens in .cpp files, you can include the lightweight companion header instead:
#include "glaze/json/generic_fwd.hpp"
This header provides the complete glz::generic / glz::generic_json type along with its inline accessors (get, get_if, holds, operator[], at, contains, is_*, size, empty, reset, the in-place operator= overloads for scalars / strings / arrays / objects, etc.), the glz::meta specialization, and the type aliases (glz::generic, glz::generic_i64, glz::generic_u64, glz::generic_sorted*). That's enough to declare APIs and manipulate values in memory.
What requires the full glaze/json/generic.hpp (and produces a linker error if called without it):
generic.dump()and any of the freeread_json/readoverloads that take ageneric_json- The templated
operator=(T&&)for arbitrary user types (the one that round-trips through JSON) glz::get<T>(generic, json_ptr)for container types,glz::navigate_to,glz::seek, andglz::convert_from_generic
Typical pattern:
// my_api.hpp - included widely; stays cheap to compile
#include "glaze/json/generic_fwd.hpp"
struct request {
std::string method;
glz::generic params;
};
void handle(const request& r);
// my_api.cpp - the only place that needs read/write
#include "glaze/json/generic.hpp"
#include "my_api.hpp"
void handle(const request& r) {
auto serialized = r.params.dump();
// ...
}
This is the same split as glaze/forward.hpp, which provides lightweight forward declarations for glz::meta, glz::to, glz::from, and glz::custom_t.
See Also
- Lazy JSON - On-demand parsing for selective field access (faster for extracting few fields)
- JSON Patch (RFC 6902) - Apply structured patches to
glz::genericdocuments - JSON Merge Patch (RFC 7386) - Apply partial updates to
glz::genericdocuments - JSON Pointer Syntax - Path syntax for navigating JSON documents
- Ordered Maps - Details on
glz::ordered_small_mapandglz::ordered_map