DAW JSON Link
Loading...
Searching...
No Matches
Classes

Arbitrary C++ data structures are supported and can be described using the trait json_data_contract.

Basic class mapping

Below is an ordinary JSON object

{
"member0": "this is a test",
"member1": 314159,
"member2": true
}

The above JSON describes a class with three members, a string named member0, an integer named member1, and a boolean named member2

Below is the C++ data structure and the trait to map the members to that of the JSON object. Note that the names of the C++ data members do not have to be the same as the JSON object's. To see a working example using this code, refer to cookbook_class1_test.cpp

++
struct MyClass1 {
std::string member_0;
int member_1;
bool member_2;
};
namespace daw::json {
template<>
struct json_data_contract<MyClass1> {
using type = json_member_list<
json_string<"member0">,
json_number<"member1", int>,
json_bool<"member2">
>;
static inline auto to_json_data( MyClass1 const &value ) {
return std::forward_as_tuple(
value.member_0,
value.member_1,
value.member_2 );
}
};
}
Customization point traits.

The above json_data_contract trait maps the JSON members to the constructor of MyClass1 in the order specified. The arguments of type std::string, int, bool will be passed.

Empty classes

An empty, default-constructible class without an explicit json_data_contract is automatically treated as an empty JSON object mapping.

++
struct EmptyClass {};
auto value = daw::json::from_json<EmptyClass>( "{}" );
auto json = daw::json::to_json( value );
// json == "{}"
DAW_CPP20_CX_ALLOC std::string to_json(Value const &value, options::output_flags_t< PolicyFlags... > flgs)
Definition daw_to_json.h:78

If an empty class has an explicit json_data_contract, that mapping is used instead. The automatic empty-object mapping is only a fallback. This allows an empty class to be used wherever an unnamed mapping can be deduced, such as an array element or variant alternative.

Class as a member

The serializing and deserializing is recursive. So if a class contains another class, the requirement is that that class has been mapped already. Assuming we already have the mapping above, lets embed that into another class

{
"a": {
"member0": "this is a test",
"member1": 314159,
"member2": true
},
"b": 1234
}

The JSON object that has a member "a" that matches MyClass1, and a second member is an unsigned To see a working example using this code, refer to cookbook_class2_test.cpp

++
// Code from previous MyClass1 example
struct MyClass2 {
MyClass1 a;
unsigned b;
};
namespace daw::json {
template<>
struct json_data_contract<MyClass2> {
using type = json_member_list<
json_class<"a", MyClass1>,
json_number<"b", unsigned>
>;
static inline auto
to_json_data( MyClass2 const &value ) {
return std::forward_as_tuple( value.a, value.b );
}
};
} // namespace daw::json

Recursive classes

A recursive class needs indirection so that the C++ type has a finite size. For example, a linked node can own the next node through a std::shared_ptr:

{
"value": 1,
"next": {
"value": 2,
"next": {
"value": 3
}
}
}

To see a working example, including serialization and round-trip parsing, refer to cookbook_class4_test.cpp.

Writing json_class_null<"next", std::shared_ptr<LinkedNode>> directly would require the LinkedNode data contract while that same contract is still being defined. An unnamed json_raw mapping delays parsing the nested object until the outer contract is complete. Wrapping it in json_nullable also permits the last node to omit "next".

++
struct LinkedNode {
int value;
std::shared_ptr<LinkedNode> next;
};
struct LinkedNodeConstructor {
LinkedNode operator()( char const *ptr, std::size_t size ) const;
};
namespace daw::json {
template<>
struct json_data_contract<LinkedNode> {
using type = json_member_list<
json_number<"value", int>,
json_nullable<
"next", std::shared_ptr<LinkedNode>,
json_raw_no_name<LinkedNode, LinkedNodeConstructor>>
>;
static std::tuple<int, std::optional<std::string>>
to_json_data( LinkedNode const &node );
};
}
LinkedNode LinkedNodeConstructor::operator()( char const *ptr,
std::size_t size ) const {
return daw::json::from_json<LinkedNode>(
std::string_view( ptr, size ) );
}
std::tuple<int, std::optional<std::string>>
LinkedNode const &node ) {
if( not node.next ) {
return { node.value, std::nullopt };
}
return { node.value, daw::json::to_json( *node.next ) };
}
Mapping class for JSON data structures to C++. It must be specialized in order to parse to a user cla...

The string returned for next contains complete JSON and is emitted verbatim by json_raw. This representation is suitable for acyclic structures such as linked lists and trees. Cyclic object graphs should instead store nodes and edges separately and resolve references by ID, as demonstrated in the graphs cookbook.

Selective mapping

Not all the JSON objects members need to be mapped. Below is the same JSON object as in the MyClass2 example above. To see a working example using this code, refer to cookbook_class3_test.cpp

{
"a": {
"member0": "this is a test",
"member1": 314159,
"member2": true
},
"b": 1234
}
++
// Code from previous MyClass1 example
struct MyClass3 {
MyClass1 a;
};
namespace daw::json {
template<>
struct json_data_contract<MyClass3> {
using type = json_member_list<json_class<"a", MyClass1>>;
static inline auto
to_json_data( MyClass3 const &value ) {
return std::forward_as_tuple( value.a );
}
};
}

Only the "a" member is mapped, the "b" member of the JSON object is ignored.

Alternate Class Mappings

If a type is already mapped or has many possible JSON mappings you can provide an alternate mapping. This is accomplished by mapping to the daw::json::json_alt<Type, Index> type. The optional index allows for any number of mappings that fit into a std::size_t.

To see a working example using this code, refer to cookbook_class_alternate1_test.cpp

++
struct Thing {
int a;
};
namespace daw::json {
template<>
struct json_data_contract<Thing> {
using type = json_member_list<json_number<"a", int>>;
};
struct json_data_contract<json_alt<Thing>> {
using type = json_ordered_member_list<int>;
};
}
//...
Thing a = daw::json::from_json<Thing>( json_string_a );
Thing b = daw::json::from_json<daw::json::json_alt<Thing>>( json_string_b );

Class construction

If constructing the class requires extra steps than passing the parsed members to it's constructor one can provide a custom Constructor callable as the default for that type or for a specific json_class mapping.

Specifying the classes default

If one wants to change the default Constructor type of a specific type they can add a type alias in that classes json_data_contract. For instance, if a member needs extra checking that the types constructor does not provide:

struct FooConstructor {
constexpr Foo operator( )( std::string name, .... ) const {
if( name != "bobfred" ) { throw badname{}; }
return Foo{ name, .... };
}
};
template<>
struct json_data_constract<Foo> {
using constructor_t = FooConstructor;
using type = json_member_list<json_string<"name">,...>;
};

Here the type Foo will use a callable of type FooConstructor to create all Foo's in the library unless overridden by a mapping.

Per mapping

The json_class mapping's second parameter is that of a Constructor type to call to construct that class and override the default. It can be specified like

json_class<"name", FooConstructor>