Variant or sum types are where the json member can have more than one type(like string, number, class, or array). JSON Link supports several forms of variant types
- An undiscriminated variant that is limited to a set of alternatives where there is up to one of each of the basic JSON types(array, object, string, number, bool, null).
- A discriminated variant where the discriminator is another member of the same class. e.g.
json { "type": 1, "value": [] }
where "value"
's type is determined by "type"
- A discriminated variant where the discriminator is a submember of the class type being parsed. e.g
json { "obj": { "type": 1 } }
where "type" determines how the class itself will be parsed
Take the following JSON array:
[
{
"member0": 5,
"member1": "hello"
},
{
"member0": "world",
"member1": true
}
]
Here we have an array of a class that has two members. A variant of types number and string, member0; and a variant of type string and bool, member1.
Too see a working example using this code, refer to cookbook_variant1_test.cpp
The following C++ can provide a mapping. It, also, highlights that the types bool, integers, floating point, std:: string, and previously mapped types can be default mapped to elements that do not require a name. Such as variant, array, and some key_value's.
{c++}
struct MyVariantStuff1 {
std::variant<int, std::string> member0;
std::variant<std::string, bool> member1;
};
namespace daw::json {
template<>
struct json_data_contract<MyVariantStuff1> {
using type = json_member_list<
json_variant<"member0", std::variant<int, std::string>,
json_variant_type_list<int,
json_string_no_name<>>>,
json_variant<
"member1", std::variant<std::string, bool>,
json_variant_type_list<std::string, json_bool_no_name<>>>>;
static inline auto
to_json_data( MyVariantStuff1 const &value ) {
return std::forward_as_tuple( value.member0, value.member1 );
}
};
}
Important note:
The elements in the json_variant_type_list
must have matching types in the variant alternatives. (e.g. std::string -> json_string, bool -> json_bool )
Tagged Variants
It is common to have a tag discriminator in JSON data. The json_tagged_variant
member type allows using another parsed member to return an index in a member list to parse. This allows any number of types to be inside the variant.
Below is a JSON array, containing a variant element where the "type"
member determines the type of the "value"
member. In many JSON documents, the discriminator will be a string.
[
{
"type": 0,
"name": "string value",
"value": "hello"
},
{
"type": 1,
"name": "int value",
"value": 5
},
{
"type": 2,
"name": "bool value",
"value": false
}
]
A member name and a callable are needed to tell the parser which type will parsed.
Too see a working example using this code, refer to cookbook_variant2_test.cpp
{c++}
struct MyClass {
std::string name;
std::variant<std::string, int, bool> value;
};
struct MyClassSwitcher {
// Convert JSON tag member to type index
constexpr size_t operator( )( int type ) const {
return (size_t)type;
}
// Get value for Tag from class value
int operator( )( MyClass const & v ) const {
return (int)v.value.index( );
}
};
template<>
struct daw::json::json_data_contract<MyClass> {
using type = json_member_list<
json_string<"name">,
json_tagged_variant<
"value",
std::variant<std::string, int, bool>,
json_number<"type", int>,
MyClassSwitcher>
>;
static constexpr inline to_json_data( MyClass const & v ) {
return std::forward_as_tuple( v.name, v.value );
}
};
In the above example, two members are mapped to construct MyClass, "name"
and "value"
. The variant uses the JSON "type"
member to determine the index of the parser to use for the variant value.
Extending the previous example, it auto detected the std::string
, int
, and bool
types and supplied the parser descriptions for them. Lets do it manually.
Below is a JSON array, containing a variant element where the "type"
member determines the type of the "value"
member. In many JSON documents, the discriminator will be a string.
[
{
"type": 0,
"name": "string value",
"value": "hello"
},
{
"type": 1,
"name": "int value",
"value": 5
},
{
"type": 2,
"name": "bool value",
"value": false
}
]
A member name and a Callable are needed to tell the parser which type will parsed.
Too see a working example using this code, refer to cookbook_variant3_test.cpp
{c++}
struct MyClass {
std::string name;
std::variant<std::string, int, bool> value;
};
struct MyClassSwitcher {
// Convert JSON tag member to type index
constexpr size_t operator( )( int type ) const {
return (size_t)type;
}
// Get value for Tag from class value
int operator( )( MyClass const & v ) const {
return (int)v.index( );
}
};
template<>
struct daw::json::json_data_contract<MyClass> {
using type = json_member_list<
json_string<"name">,
json_tagged_variant<
"value",
std::variant<std::string, int, bool>,
json_number<"type", int>,
MyClassSwitcher,
json_tagged_variant_type_list<
std::string,
json_number_no_name<int>,
json_bool_no_name<>
>
>
>;
static constexpr to_json_data( MyClass const & v ) {
return std::forward_as_tuple( v.name, v.value );
}
};
As you can see, the json_variant_type_list can use terse type names for some, or the full names.
Submember as tag for tagged_variant
There are cases where a classes structure is determined by one of it's submembers. This comes up with file versioning.
In our example we have two versions of a config file. The tag member "version"
determines the layout of the other members in the example.
{
"version": 1,
"name": "what is the answer to the ultimate question?",
"value": 42,
"next question": "what is earth"
}
{
"version": 2,
"config_options": [
{
"name": "bob",
"value": 42
}
],
"option2": 5
}
The above example shows two distinct JSON objects that both have a "version"
member that is a discriminator for the expected data structure.
namespace version1 {
struct Config {
int version;
std::string name;
int value;
std::string next_question;
};
}
namespace version2 {
struct Config {
int version;
std::map<std::string, int> config_options;
int option2;
};
}
using configs_t = std::variant<version1::Config, version2::Config>;
struct Switcher {
size_t operator( )( int type ) const {
return static_cast<std::size_t>( type - 1 );
}
std::size_t operator( )( configs_t const &v ) const {
return static_cast<std::size_t>( v.index( ) );
}
};
template<>
struct json_data_contract<version1::Config> {
using type = json_member_list<
json_number<"version", int>,
json_string<"name">,
json_number<"value", int>,
json_string<"next question">
>;
static constexpr auto to_json_data( version1::Config const & v ) {
return std::forward_as_tuple( v.version, v.name, v.value, v.next_question );
}
};
template<>
struct json_data_contract<version2::Config> {
using type = json_member_list<
json_number<"version", int>,
json_key_value_array<"config_options",
std::map<std::string, int>,
json_number<"value", int>,
json_string<"name">
>,
json_number<"option2", int>
>;
static constexpr auto to_json_data( version2::Config const & v ) {
return std::forward_as_tuple( v.version, v.config_options, v.option2 );
}
};
template<>
struct json_data_contract<
std::variant<version1::Config, version2::Config>> {
using type = json_submember_tagged_variant<
json_number<"version", int>,
Switcher,
version1::Config,
version2::Config
>;
};
}
Customization point traits.
missing_json_data_contract_for_or_unknown_type< T > type