| Home | Internal Documentation |
Libraries: Mug · ThorsMug · ThorsSlack · NisseServer · NisseHTTP · ThorsSocket · ThorsCrypto · ThorsSerializer · ThorsMongo · ThorsLogging · ThorsIOUtil
Declarative C++ serialization for JSON, YAML, and BSON. Instead of manually building a DOM or writing conversion code, declare which members of your C++ types should be serialized and the library generates everything at compile time.
Namespace: ThorsAnvil::Serialize
Serialize a std::vector<int> to/from JSON in under 10 lines:
CXXFLAGS = -std=c++20
LDLIBS = -lThorsLogging -lThorSerialize
all: ThorsSerApp
#include <iostream>
#include <vector>
#include "ThorSerialize/JsonThor.h"
int main()
{
using ThorsAnvil::Serialize::jsonImporter;
using ThorsAnvil::Serialize::jsonExporter;
std::vector<int> data;
std::cin >> jsonImporter(data); // read JSON from stdin
std::cout << jsonExporter(data) << "\n"; // write JSON to stdout
}
echo "[1,2,3,4,5]" | ./a.out
# Output: [1, 2, 3, 4, 5]
| Header | Purpose |
|---|---|
ThorSerialize/Traits.h |
Core macros for declaring serializable types |
ThorSerialize/SerUtil.h |
Support for standard library containers |
ThorSerialize/JsonThor.h |
JSON exporter/importer |
ThorSerialize/YamlThor.h |
YAML exporter/importer |
ThorSerialize/BsonThor.h |
BSON exporter/importer |
Use ThorsAnvil_MakeTrait() at global scope (outside any namespace):
struct Color
{
int red;
int green;
int blue;
};
ThorsAnvil_MakeTrait(Color, red, green, blue);
If Color is in the namespace Graphics:
ThorsAnvil_MakeTrait(Graphics::Color, red, green, blue);
Declare Traits<T> as a friend:
class User
{
friend class ThorsAnvil::Serialize::Traits<User>;
std::string name;
int age;
public:
User() : name(""), age(0) {}
User(std::string n, int a) : name(std::move(n)), age(a) {}
};
ThorsAnvil_MakeTrait(User, name, age);
Use ThorsAnvil_ExpandTrait() to include parent fields automatically:
struct Base { int value1; int value2; };
struct Derived : public Base { int value3; int value4; };
ThorsAnvil_MakeTrait(Base, value1, value2);
ThorsAnvil_ExpandTrait(Base, Derived, value3, value4);
// Serializing Derived includes value1, value2, value3, value4
template<typename T>
struct Wrapper { T data; };
ThorsAnvil_Template_MakeTrait(1, Wrapper, data); // 1 = number of template params
#include "ThorSerialize/JsonThor.h"
using namespace ThorsAnvil::Serialize;
MyType obj;
// Write to stdout
std::cout << jsonExporter(obj);
// Write to a string
std::string output;
output << jsonExporter(obj);
// YAML / BSON
std::cout << yamlExporter(obj);
std::cout << bsonExporter(obj);
using namespace ThorsAnvil::Serialize;
MyType obj;
// Read from stdin
std::cin >> jsonImporter(obj);
// Read from a string
std::string input = R"({"name": "Alice", "age": 30})";
input >> jsonImporter(obj);
// Build in one expression
auto user = jsonBuilder<User>(std::string{R"({"name":"Alice","age":30})"});
std::cout << jsonExporter(myObj, PrinterConfig{}
.setOutputType(OutputType::Stream)
.setPolymorphicMarker("TypeName")
.setCatchExceptions(true)
.setCatchUnknownExceptions(true)
.setExactPreFlightCalc(false)
.setTabSize(4)
.setBlockSize(8)
);
| Method | Default | Description |
|---|---|---|
| setOutputType() | OutputType::Default | How to serialize an object. OutputType::Config => User readable. OutputType::Stream => Single line no space. |
| setPolymorphicMarker() | ”” | Polymorphic class type marker. If <Empty String> <Type>::polyname() if it exists. Default value is “__type”. |
| setCatchExceptions() | true | If true, catch exception derived from std::exception and set the bad bit of the stream. |
| setCatchUnknownExceptions() | false | If true, catch any other exception and set the bad bit of the stream. DO NOTset this. boost co-routine shut down exception will fail to propogate correctly. |
| setExactPreFlightCalc() | false | If false, When exporting to a string we preflight the printing to calculate the size so there is only one allocation. |
| setTabSize() | JSON => 4 YAML => 2 |
Set the tab indent size. |
| setBlockSize() | 0 | Set the indent from left side. |
IgnoreCallBack ignoreCB;
std::cin >> jsonImporter(myObj, ParserConfig{}
.setParseStrictness(OutputType::Stream)
.setPolymorphicMarker("TypeName")
.setCatchExceptions(true)
.setCatchUnknownExceptions(false)
.setValidateNoTrailingData(true)
.setNoBackslashConversion(false)
.setIdentifyDynamicClass([](){return "Alternative";})
.setIgnoreCallBack(std::move(ignoreCB))
);
| Method | Default | Description |
|---|---|---|
| setParseStrictness() | ParseType::Weak | ParseType::Weak => No checks. ParseType::Strict => Error if unexpected field. ParseType::Exact => Error if missing any fields or extra fields. |
| setPolymorphicMarker() | ”” | Polymorphic class type marker. If <Empty String> <Type>::polyname() if it exists. Default value is “__type”. |
| setCatchExceptions() | true | If true, catch exception derived from std::exception and set the bad bit of the stream. |
| setCatchUnknownExceptions() | false | If true, catch any other exception and set the bad bit of the stream. DO NOTset this. boost co-routine shut down exception will fail to propogate correctly. |
| setValidateNoTrailingData() | false | If true, validate no JSON tokens after reading object. |
| setNoBackslashConversion() | true | if true, convert backslash characters as per JSON spec, otherwise ignore. |
| setIdentifyDynamicClass() | std::function<std::string(DataInputStream&)> | Identify polymorphic type using non standard technique. |
| setIgnoreCallBack() | IgnoreCallBack | Send ignored data to object. |
Include ThorSerialize/SerUtil.h to get automatic support:
| Type | Serialized As |
|---|---|
std::vector<T>, std::list<T>, std::deque<T> |
array |
std::set<T>, std::unordered_set<T> |
array |
std::array<T, N>, std::tuple<Args...> |
array |
std::map<std::string, V> |
object |
std::map<K, V> (non-string keys) |
array of pairs |
std::pair<F, S> |
object with "first", "second" |
std::optional<T> |
value if present, omitted if empty |
std::unique_ptr<T> |
value if non-null, null otherwise |
std::shared_ptr<T> |
deduplicated serialization |
std::variant<Args...> |
serialized as the active alternative |
Primitive types (int, double, bool, std::string, etc.) are handled automatically.
Enums are serialized as string names automatically via magic_enum:
enum class Color { Red, Green, Blue };
struct Shirt { Color color; };
ThorsAnvil_MakeTrait(Shirt, color);
// Serializes as: {"color": "Red"}
Map C++ field names to different serialized names:
struct MongoQuery { std::string database; std::string collection; };
ThorsAnvil_MakeOverride(MongoQuery, {"database", "$db"}, {"collection", "$coll"});
ThorsAnvil_MakeTrait(MongoQuery, database, collection);
// Produces: {"$db": "mydb", "$coll": "users"}
Fields declared as std::optional<T> are omitted from output when empty:
struct UserProfile
{
std::string name;
std::optional<int> age;
std::optional<std::string> email;
};
ThorsAnvil_MakeTrait(UserProfile, name, age, email);
UserProfile u{"Alice", std::nullopt, "alice@example.com"};
std::cout << jsonExporter(u);
// Output: {"name": "Alice", "email": "alice@example.com"}
Serialize base-class pointers with automatic type discrimination:
struct Transport
{
int wheelCount;
virtual ~Transport() = default;
ThorsAnvil_PolyMorphicSerializer(Transport);
};
struct Car : public Transport
{
std::string engineType;
ThorsAnvil_PolyMorphicSerializerWithOverride(Car);
};
ThorsAnvil_MakeTrait(Transport, wheelCount);
ThorsAnvil_ExpandTrait(Transport, Car, engineType);
// Serialized JSON includes "__type" discriminator:
// {"__type": "Car", "wheelCount": 4, "engineType": "V8"}
For types that need non-standard serialization:
struct MyTypeSerializer
{
static void writeCustom(PrinterInterface& printer, MyType const& object);
static void readCustom(ParserInterface& parser, MyType& object);
};
ThorsAnvil_MakeTraitCustomSerialize(MyType, MyTypeSerializer);
#include <iostream>
#include <sstream>
#include "ThorSerialize/Traits.h"
#include "ThorSerialize/JsonThor.h"
struct Shirt { int red; int green; int blue; };
class TeamMember
{
friend class ThorsAnvil::Serialize::Traits<TeamMember>;
std::string name;
int score;
int damage;
Shirt team;
public:
TeamMember() : name(""), score(0), damage(0), team{0,0,0} {}
TeamMember(std::string n, int s, int d, Shirt t)
: name(std::move(n)), score(s), damage(d), team(t) {}
};
ThorsAnvil_MakeTrait(Shirt, red, green, blue);
ThorsAnvil_MakeTrait(TeamMember, name, score, damage, team);
int main()
{
using ThorsAnvil::Serialize::jsonExporter;
using ThorsAnvil::Serialize::jsonImporter;
// Serialize
TeamMember mark("Mark", 10, 5, {255, 0, 0});
std::cout << jsonExporter(mark) << "\n";
// Deserialize
TeamMember john;
std::stringstream input(
R"({"name":"John","score":13,"damage":2,"team":{"red":0,"green":0,"blue":255}})");
input >> jsonImporter(john);
std::cout << jsonExporter(john) << "\n";
}
| Macro | Usage |
|---|---|
ThorsAnvil_MakeTrait(Type, members...) |
Declare a serializable type |
ThorsAnvil_ExpandTrait(Parent, Type, members...) |
Declare a derived serializable type |
ThorsAnvil_Template_MakeTrait(N, Type, members...) |
Serializable template type |
ThorsAnvil_Template_ExpandTrait(N, Parent, Type, members...) |
Derived template type |
ThorsAnvil_MakeOverride(Type, {"from","to"}...) |
Rename fields in output |
ThorsAnvil_MakeTraitCustomSerialize(Type, Serializer) |
Custom serialization |
ThorsAnvil_PolyMorphicSerializer(Type) |
Polymorphic base class |
ThorsAnvil_PolyMorphicSerializerWithOverride(Type) |
Polymorphic derived class |
ThorsAnvil_PointerAllocator(Type, Allocator) |
Custom pointer allocation |