A type-safe, reflection-based serialization and deserialization library for C++23.
This library provides automatic serialization and deserialization of C++ types using compile-time reflection. It supports built-in types, user-defined structs, pointers, and vectors with built-in type safety through metadata tagging.
- Automatic serialization/deserialization of:
- Built-in types (int, char, float, double, bool, etc.)
- User-defined structs
- Pointers (with null handling)
- Vectors (including nested vectors)
- Type safety: Metadata tags ensure deserializing into the wrong type is caught at runtime
- Endianness handling: Automatically converts to/from little-endian format
- Compile-time reflection: Uses descriptor pattern for zero-overhead type introspection
- Detailed error messages: Clear error reporting when type mismatches occur
- C++23 compiler (Clang with libc++)
- Bazel build system
- Dependencies:
- Development dependencies:
- GoogleTest (v1.16.0) for testing
# Build the library
bazel build //:serdeTo make a struct serializable, define a descriptor_of specialization:
#include "serde/serialize.hh"
#include "serde/deserialize.hh"
struct Person
{
char initial;
int age;
double height;
};
// Define the descriptor
template <>
constexpr auto descriptor_of<Person>()
{
return std::make_tuple(&Person::initial, &Person::age, &Person::height);
}Person person{'J', 30, 5.9};
// Serialize to bytes
std::vector<std::byte> bytes = serde::serialize(person);// Deserialize from bytes
Person restored = serde::deserialize<Person>(bytes);struct Node
{
int value;
Node* next;
std::vector<int> data;
};
template <>
constexpr auto descriptor_of<Node>() {
return std::make_tuple(&Node::value, &Node::next, &Node::data);
}
// Create a linked list
Node* second = new Node{2, nullptr, {20, 21, 22}};
Node first{1, second, {10, 11, 12}};
// Serialize and deserialize
auto bytes = serde::serialize(first);
Node restored = serde::deserialize<Node>(bytes);The library uses hash-based type tags to ensure type safety during deserialization:
auto bytes = serde::serialize(42);
// This will throw std::logic_error with a descriptive message
auto wrong = serde::deserialize<double>(bytes);
// Error: "Metadata mismatch: expecting 'double' but found 'int' starting at byte X"Each serialized value is prefixed with:
- A type tag (64-bit hash of the type)
- The actual data in little-endian format
For complex types:
- Structs: Tag + serialized members in order
- Vectors: Tag + size (with tag) + serialized elements
- Pointers: Tag + null flag (0 or 1) + serialized pointee (if non-null)
Type tags are computed at compile-time using:
- String hashing for built-in types and vectors
- Recursive member hashing for user-defined structs
This ensures consistent tagging across compilation units while maintaining type safety.
The test suite covers:
- Built-in types (int, char, bool, double)
- Pointers (including null pointers)
- User-defined structs (simple and nested)
- Vectors of built-ins and structs
- Type mismatch detection
- Missing metadata detection
Run tests with:
bazel test //:test