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 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
|
# Variant Handling
Glaze has full support for `std::variant` when writing, and comprehensive read support with automatic type deduction. When reading JSON into a variant, Glaze can automatically determine the correct type based on the JSON structure, field presence, or explicit type tags.
## Basic Types
Types can be auto-deduced if the variant contains at most one type matching each of the fundamental JSON types of [string, number, object, array, boolean]. For auto-deduction to work:
- `std::variant<double, std::string>` ✅ (one number type, one string type)
- `std::variant<double, float>` ❌ (two number types - requires explicit handling)
- `std::variant<bool, std::string, double>` ✅ (one of each type)
Write example:
```c++
std::variant<double, std::string> d = "not_a_fish";
auto s = glz::write_json(d);
expect(s == R"("not_a_fish")");
```
Read example:
```c++
std::variant<int32_t, double> x = 44;
glz::read_json(x, "33");
expect(std::get<int32_t>(x) == 33);
```
### Multiple Types of Same Category
Glaze can handle variants with multiple types of the same JSON category (e.g., multiple array types or multiple number types). When auto-deduction isn't possible, Glaze will try each alternative in order until one successfully parses:
```c++
// Multiple array types - now supported!
using NestedArrayVariant = std::variant<std::vector<double>, std::vector<std::vector<double>>>;
NestedArrayVariant var;
glz::read_json(var, "[1.0, 2.0, 3.0]"); // → std::vector<double>
glz::read_json(var, "[[1.0, 2.0], [3.0, 4.0]]"); // → std::vector<std::vector<double>>
```
This works for any combination of types:
```c++
// Multiple number types
using NumberVariant = std::variant<int, float, double>;
NumberVariant n;
glz::read_json(n, "42"); // Tries int first, succeeds
glz::read_json(n, "3.14"); // Tries int (fails), then float (succeeds)
```
## Custom Types in Variants
When using custom types in variants, Glaze needs to know what JSON type your custom type corresponds to. There are two approaches:
### Option 1: Using `mimic` (Simple Member Pointer)
For types that serialize via a simple member pointer, use `using mimic = <type>`:
```c++
struct my_key {
std::string value{};
};
template <>
struct glz::meta<my_key> {
using mimic = std::string; // Declare that this type behaves like a string
static constexpr auto value = &my_key::value;
};
// Now works in variants and as map keys
std::variant<int, my_key> v;
glz::read_json(v, "\"hello\""); // → my_key{"hello"}
```
### Option 2: Auto-Inference (Custom Read/Write)
For types using `glz::custom`, Glaze automatically infers the JSON type from your read function's parameter:
```c++
struct Percentage {
double value{};
};
template <>
struct glz::meta<Percentage> {
// Glaze infers this is a number from 'const double&'
static constexpr auto read_fn = [](Percentage& p, const double& input) {
p.value = input;
};
static constexpr auto write_fn = [](const Percentage& p) -> const double& {
return p.value;
};
static constexpr auto value = glz::custom<read_fn, write_fn>;
};
// Works automatically - no mimic needed!
std::variant<std::string, Percentage> v;
glz::read_json(v, "1.5"); // → Percentage{1.5}
```
| Read parameter type | JSON type detected | Concept satisfied |
|--------------------|-------------------|-------------------|
| `double`, `int`, etc. | number | `custom_num_t<T>` |
| `std::string`, `std::string_view` | string | `custom_str_t<T>` |
| `bool` | boolean | `custom_bool_t<T>` |
### Complete Example
```c++
#include "glaze/glaze.hpp"
// Strong typedef for monetary amounts
struct Amount {
double value{};
};
template <>
struct glz::meta<Amount> {
static constexpr auto read_fn = [](Amount& a, const double& input) {
a.value = input;
};
static constexpr auto write_fn = [](const Amount& a) -> const double& {
return a.value;
};
static constexpr auto value = glz::custom<read_fn, write_fn>;
};
struct Transaction {
std::string description;
std::variant<std::string, Amount> value;
};
void example() {
Transaction t1;
glz::read_json(t1, R"({"description":"Payment","value":99.99})");
// t1.value holds Amount{99.99}
Transaction t2;
glz::read_json(t2, R"({"description":"Note","value":"Pending approval"})");
// t2.value holds std::string{"Pending approval"}
}
```
### Compile-Time Concepts
```c++
// For mimic-based types
static_assert(glz::has_mimic<my_key>);
static_assert(glz::mimics<my_key, std::string>);
static_assert(glz::mimics_str_t<my_key>);
// For auto-inferred types
static_assert(glz::custom_num_t<Percentage>);
static_assert(glz::custom_str_t<StrongString>);
```
### Important Notes
1. **Choose the right approach**: Use `mimic` for simple member pointers, auto-inference for `glz::custom`.
2. **Do not use both**: Never define both `mimic` and `glz::custom` on the same type. They serve different purposes and combining them may cause incorrect variant type deduction.
3. **Default constructible**: Types used in variants should have a non-explicit default constructor.
4. **One type per category**: A type can only represent one JSON type.
5. **Multiple custom types**: If you have multiple custom types of the same JSON category in a variant, Glaze will try them in order.
## Object Types
Glaze provides multiple strategies for deducing which variant type to use when reading JSON objects:
### Auto Deduction with Reflectable Structs
Simple aggregate structs can be used in variants without requiring explicit `glz::meta` specializations. Glaze automatically uses reflection to deduce the type based on field names:
```c++
// No glz::meta needed for these simple structs!
struct Book {
std::string title;
std::string author;
int pages;
};
struct Movie {
std::string director;
int duration;
float rating;
};
struct Song {
std::string artist;
std::string album;
int year;
};
// Automatic deduction based on unique field names
using MediaVariant = std::variant<Book, Movie, Song>;
MediaVariant media;
glz::read_json(media, R"({"title":"1984","author":"Orwell","pages":328})"); // → Book
glz::read_json(media, R"({"director":"Nolan","duration":148,"rating":8.8})"); // → Movie
glz::read_json(media, R"({"artist":"Beatles","album":"Abbey Road","year":1969})"); // → Song
// Even partial fields work for deduction
glz::read_json(media, R"({"title":"Partial Book"})"); // → Book (unique field)
glz::read_json(media, R"({"director":"Unknown"})"); // → Movie (unique field)
```
### Auto Deduction with Meta Objects
For more complex types or when you need custom serialization, you can still use explicit `glz::meta` specializations. Objects can be auto deduced when they have unique key combinations that distinguish them from other types in the variant.
```c++
struct xy_t
{
int x{};
int y{};
};
template <>
struct glz::meta<xy_t>
{
using T = xy_t;
static constexpr auto value = object(&T::x, &T::y);
};
struct yz_t
{
int y{};
int z{};
};
template <>
struct glz::meta<yz_t>
{
using T = yz_t;
static constexpr auto value = object(&T::y, &T::z);
};
struct xz_t
{
int x{};
int z{};
};
template <>
struct glz::meta<xz_t>
{
using T = xz_t;
static constexpr auto value = object(&T::x, &T::z);
};
suite metaobject_variant_auto_deduction = [] {
"metaobject_variant_auto_deduction"_test = [] {
std::variant<xy_t, yz_t, xz_t> var{};
std::string b = R"({"y":1,"z":2})";
expect(glz::read_json(var, b) == glz::error_code::none);
expect(std::holds_alternative<yz_t>(var));
expect(std::get<yz_t>(var).y == 1);
expect(std::get<yz_t>(var).z == 2);
b = R"({"x":5,"y":7})";
expect(glz::read_json(var, b) == glz::error_code::none);
expect(std::holds_alternative<xy_t>(var));
expect(std::get<xy_t>(var).x == 5);
expect(std::get<xy_t>(var).y == 7);
b = R"({"z":3,"x":4})";
expect(glz::read_json(var, b) == glz::error_code::none);
expect(std::holds_alternative<xz_t>(var));
expect(std::get<xz_t>(var).z == 3);
expect(std::get<xz_t>(var).x == 4);
};
};
```
In the example above, each type has a unique combination of keys (xy, yz, xz), making deduction straightforward based on which keys are present in the JSON.
### Ambiguous Variant Resolution
When multiple object types in a variant could match the input JSON (all required fields are present), Glaze automatically resolves the ambiguity by selecting the type with the **fewest fields**. This "smallest object wins" strategy ensures the most specific type is chosen.
**Example:**
```cpp
struct SwitchBlock {
int value{}; // 1 field
};
struct PDataBlock {
std::string p_id{}; // 2 fields
int value{};
};
using var_t = std::variant<SwitchBlock, PDataBlock>;
// JSON: {"value": 42}
// Both types could match, but SwitchBlock is chosen (1 field < 2 fields)
var_t v1;
glz::read_json(v1, R"({"value": 42})");
// v1 holds SwitchBlock
// JSON: {"p_id": "test", "value": 99}
// Only PDataBlock matches (has all required fields)
var_t v2;
glz::read_json(v2, R"({"p_id": "test", "value": 99})");
// v2 holds PDataBlock
```
This resolution applies when:
- The variant contains 2 or more object types
- Multiple types have all their fields present in the JSON
- The type with the minimum field count is automatically selected
Common use cases include:
- **Progressive detail levels**: Types that represent the same concept with increasing levels of detail
- **Optional field patterns**: Where simpler types are subsets of more complex types
- **API versioning**: Where newer types extend older ones with additional fields
**Complete Example with Multiple Levels:**
```cpp
struct PersonBasic {
std::string name{};
};
struct PersonWithAge {
std::string name{};
int age{};
};
struct PersonFull {
std::string name{};
int age{};
double height{};
};
using person_variant = std::variant<PersonBasic, PersonWithAge, PersonFull>;
// Automatically selects the most specific type based on fields present:
person_variant p1;
glz::read_json(p1, R"({"name": "Alice"})"); // → PersonBasic
glz::read_json(p1, R"({"name": "Bob", "age": 30})"); // → PersonWithAge
glz::read_json(p1, R"({"name": "Charlie", "age": 25, "height": 175.5})"); // → PersonFull
```
### Deduction of Tagged Object Types
If you don't want auto deduction, need explicit type control, or need to deduce the type based on the value associated with a key, Glaze supports custom tags. This is particularly useful when:
- You want explicit control over type selection regardless of field presence
- The automatic deduction might be ambiguous or unpredictable
- You need to support types with identical field structures
```c++
struct put_action
{
std::map<std::string, int> data{};
};
template <>
struct glz::meta<put_action>
{
using T = put_action;
static constexpr auto value = object(&T::data);
};
struct delete_action
{
std::string data{};
};
template <>
struct glz::meta<delete_action>
{
using T = delete_action;
static constexpr auto value = object(&T::data);
};
using tagged_variant = std::variant<put_action, delete_action>;
template <>
struct glz::meta<tagged_variant>
{
static constexpr std::string_view tag = "action";
static constexpr auto ids = std::array{"PUT", "DELETE"}; //Defaults to glz::name_v of the type if ids is not supplied
};
suite tagged_variant_tests = [] {
"tagged_variant_write_tests"_test = [] {
tagged_variant var = delete_action{{"the_internet"}};
std::string s{};
glz::write_json(var, s);
expect(s == R"({"action":"DELETE","data":"the_internet"})");
};
"tagged_variant_read_tests"_test = [] {
tagged_variant var{};
expect(glz::read_json(var, R"({"action":"DELETE","data":"the_internet"})") == glz::error_code::none);
expect(std::get<delete_action>(var).data == "the_internet");
};
};
```
### Tagged Variants with Embedded Tags
Variant tags may be embedded within the structs themselves, making the discriminator accessible at runtime without using `std::visit`. This provides cleaner, more maintainable code:
```c++
// String-based embedded tags
struct CreateAction {
std::string action{"CREATE"}; // Embedded tag field
std::string resource;
std::map<std::string, std::string> attributes;
};
struct UpdateAction {
std::string action{"UPDATE"}; // Embedded tag field
std::string id;
std::map<std::string, std::string> changes;
};
struct DeleteAction {
std::string action{"DELETE"}; // Embedded tag field
std::string id;
};
using ActionVariant = std::variant<CreateAction, UpdateAction, DeleteAction>;
template <>
struct glz::meta<ActionVariant> {
static constexpr std::string_view tag = "action"; // Field name that serves as tag
static constexpr auto ids = std::array{"CREATE", "UPDATE", "DELETE"};
};
// Writing - no double tagging, single "action" field
ActionVariant v = UpdateAction{"UPDATE", "123", {{"status", "active"}}};
auto json = glz::write_json(v);
// Result: {"action":"UPDATE","id":"123","changes":{"status":"active"}}
// Reading - tag field is populated automatically
ActionVariant v2;
glz::read_json(v2, json.value());
if (std::holds_alternative<UpdateAction>(v2)) {
auto& update = std::get<UpdateAction>(v2);
// Direct access to discriminator without std::visit!
assert(update.action == "UPDATE");
}
```
#### Enum-based Embedded Tags
For type safety, you can use enums as embedded tags:
```c++
enum class OperationType { GET, POST, PUT, DELETE };
// Define string mapping for the enum
template <>
struct glz::meta<OperationType> {
using enum OperationType;
static constexpr auto value = enumerate(GET, POST, PUT, DELETE);
};
struct GetRequest {
OperationType operation{OperationType::GET}; // Embedded enum tag
std::string path;
std::map<std::string, std::string> params;
};
struct PostRequest {
OperationType operation{OperationType::POST}; // Embedded enum tag
std::string path;
std::string body;
};
using RequestVariant = std::variant<GetRequest, PostRequest>;
template <>
struct glz::meta<RequestVariant> {
static constexpr std::string_view tag = "operation";
static constexpr auto ids = std::array{"GET", "POST"};
};
// The enum is serialized as a string in JSON
RequestVariant req = PostRequest{OperationType::POST, "/api/users", R"({"name":"Alice"})"};
auto json = glz::write_json(req);
// Result: {"operation":"POST","path":"/api/users","body":"{\"name\":\"Alice\"}"}
```
Benefits of embedded tags:
- **Runtime access**: The tag value is directly accessible without `std::visit`
- **No duplication**: JSON contains the tag field only once
- **Type safety**: Using enums provides compile-time checking
- **Cleaner code**: Keeps discriminator with the data it describes
### Tagged Variants with Reflectable Structs
You can also use tagged variants with simple reflectable structs without explicit `glz::meta` for the struct types:
```c++
// Simple structs - no glz::meta needed!
struct Person {
std::string name;
int age;
};
struct Animal {
std::string species;
float weight;
};
struct Vehicle {
std::string model;
int wheels;
};
using EntityVariant = std::variant<Person, Animal, Vehicle>;
// Only the variant needs meta for tagging
template <>
struct glz::meta<EntityVariant> {
static constexpr std::string_view tag = "type";
static constexpr auto ids = std::array{"person", "animal", "vehicle"};
};
// Writing includes the type tag
EntityVariant e = Person{"Alice", 30};
auto json = glz::write_json(e);
// Result: {"type":"person","name":"Alice","age":30}
// Reading uses the tag for type selection
EntityVariant e2;
glz::read_json(e2, R"({"type":"animal","species":"Lion","weight":190.5})");
// e2 now holds Animal{"Lion", 190.5f}
// Tag/field validation: If a tag value doesn't match the fields, an error is reported
EntityVariant e3;
auto error = glz::read_json(e3, R"({"species":"Lion","type":"person","weight":190.5})");
// Error: no_matching_variant_type (tag says "person" but fields match Animal)
```
### Default Variant Types (Catch-All Handler)
When working with tagged variants, you may encounter unknown tag values that aren't in your predefined list. Glaze supports a default/catch-all variant type by making the `ids` array shorter than the number of variant alternatives. The first unlabeled type (without a corresponding ID) becomes the default handler for unknown tags.
```c++
struct CreateAction {
std::string action{"CREATE"}; // Embedded tag field
std::string resource;
std::map<std::string, std::string> attributes;
};
struct UpdateAction {
std::string action{"UPDATE"}; // Embedded tag field
std::string id;
std::map<std::string, std::string> changes;
};
// Default handler for unknown action types
struct UnknownAction {
std::string action; // Will be populated with the actual tag value
std::optional<std::string> id;
std::optional<std::string> resource;
std::optional<std::string> target;
// Can add more optional fields to handle various unknown formats
};
using ActionVariant = std::variant<CreateAction, UpdateAction, UnknownAction>;
template <>
struct glz::meta<ActionVariant> {
static constexpr std::string_view tag = "action";
// Note: Only 2 IDs for 3 variant types - UnknownAction becomes the default
static constexpr auto ids = std::array{"CREATE", "UPDATE"};
};
// Known actions work as expected
std::string_view json = R"({"action":"CREATE","resource":"user","attributes":{"name":"Alice"}})";
ActionVariant av;
glz::read_json(av, json);
// av holds CreateAction with action == "CREATE"
// Unknown actions route to the default type
json = R"({"action":"DELETE","id":"123","target":"resource"})";
glz::read_json(av, json);
// av holds UnknownAction with action == "DELETE"
if (std::holds_alternative<UnknownAction>(av)) {
auto& unknown = std::get<UnknownAction>(av);
std::cout << "Unknown action: " << unknown.action << std::endl; // Prints: DELETE
}
```
This feature is particularly useful for:
- **Forward compatibility**: Handle future action types without breaking existing code
- **API versioning**: Gracefully handle newer message types from updated clients
- **Error recovery**: Capture and log unknown operations instead of failing
- **Plugin systems**: Allow extensions to define custom actions
The default type works with both string and numeric tags:
```c++
struct TypeA {
int type{1};
std::string data;
};
struct TypeB {
int type{2};
double value;
};
struct TypeDefault {
int type; // Will contain the actual numeric tag value
std::optional<std::string> data;
std::optional<double> value;
};
using NumericVariant = std::variant<TypeA, TypeB, TypeDefault>;
template <>
struct glz::meta<NumericVariant> {
static constexpr std::string_view tag = "type";
static constexpr auto ids = std::array{1, 2}; // TypeDefault handles all other values
};
// Type 99 is unknown, routes to TypeDefault
std::string_view json = R"({"type":99,"data":"unknown"})";
NumericVariant nv;
glz::read_json(nv, json);
// nv holds TypeDefault with type == 99
```
**Important notes:**
- The default type must be the last type in the variant that doesn't have a corresponding ID
- The tag field in the default type will be populated with the actual tag value from the JSON
- Only the first unlabeled type serves as the default (you can only have one default handler)
## Variants with Smart Pointers
Glaze fully supports `std::unique_ptr` and `std::shared_ptr` as variant alternatives, including both auto-deduction and tagged variants. This is particularly useful for polymorphic types, factory patterns, and managing object ownership in variant containers.
### Auto-Deduction with Smart Pointers
Smart pointers work seamlessly with auto-deduction based on field names:
```c++
struct Cat {
std::string name;
int lives;
};
struct Dog {
std::string name;
std::string breed;
};
// Variant of smart pointers - auto-deduction works!
using PetVariant = std::variant<std::unique_ptr<Cat>, std::unique_ptr<Dog>>;
PetVariant pet;
glz::read_json(pet, R"({"name":"Fluffy","lives":9})");
// pet holds std::unique_ptr<Cat>
glz::read_json(pet, R"({"name":"Rover","breed":"Labrador"})");
// pet holds std::unique_ptr<Dog>
```
### Tagged Variants with Smart Pointers
Tagged variants also work with smart pointers, providing explicit type control:
```c++
struct SiteDiagnostic {
std::string message;
int severity;
};
struct DerivedSite {
std::string message;
int severity;
std::string additional_info;
};
using DiagnosticVariant = std::variant<
std::unique_ptr<SiteDiagnostic>,
std::unique_ptr<DerivedSite>
>;
template <>
struct glz::meta<DiagnosticVariant> {
static constexpr std::string_view tag = "type";
static constexpr auto ids = std::array{"SiteDiagnostic", "DerivedSite"};
};
// Writing includes the type tag
DiagnosticVariant diag = std::make_unique<SiteDiagnostic>(
SiteDiagnostic{"Error occurred", 3}
);
auto json = glz::write_json(diag);
// Result: {"type":"SiteDiagnostic","message":"Error occurred","severity":3}
// Reading uses the tag for type selection
DiagnosticVariant parsed;
glz::read_json(parsed, R"({"type":"DerivedSite","message":"Warning","severity":2,"additional_info":"Details"})");
// parsed holds std::unique_ptr<DerivedSite>
```
**Important notes:**
- Smart pointers are automatically allocated during deserialization
- Null smart pointers during serialization will result in an error
- Both auto-deduction and tagged variants work with smart pointers
- Mixed variants (e.g., `std::variant<Cat, std::unique_ptr<Dog>>`) are also supported
## BEVE to JSON
BEVE uses the variant index to denote the type in a variant. When calling `glz::beve_to_json`, variants will be written in JSON with `"index"` and `"value"` keys. The index indicates the type, which would correspond to a `std::variant` `index()` method.
```json
{
"index": 1,
"value": "my value"
}
```
> BEVE conversion to JSON does not support `string` tags, to simplify the specification and avoid bifurcation of variant handling. Using the index is more efficient in binary and more directly translated to `std::variant`.
|