Last active
September 29, 2023 00:25
Binary Serializer/Deserializer for C++ structs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* serialization.hh v1.2 by maxrt101 | |
* Serializer/Deserializer for C++ objects | |
* To make class serializable, add MAKE_SERIALIZABLE() macro inside it's declaration | |
* and pass all fields into it. | |
*/ | |
#ifndef _MRT_SERIALIZATION_H_ | |
#define _MRT_SERIALIZATION_H_ 1 | |
#include <cstdio> | |
#include <vector> | |
#include <string> | |
#include <memory> | |
#include <tuple> | |
#include <map> | |
constexpr uint32_t SERIALIZER_VERSION = 0x1; | |
#define GENERATE_TO_TUPLE(...) \ | |
inline auto toTuple() const { \ | |
auto [__VA_ARGS__] = *this; \ | |
return std::make_tuple(__VA_ARGS__); \ | |
} | |
#define GENERATE_TO_REF_TUPLE(...) \ | |
inline auto toRefTuple() { \ | |
return std::tie(__VA_ARGS__); \ | |
} | |
#define MAKE_SERIALIZABLE(...) \ | |
GENERATE_TO_TUPLE(__VA_ARGS__) \ | |
GENERATE_TO_REF_TUPLE(__VA_ARGS__) | |
namespace mrt { | |
template <class Tuple, class Fn, std::size_t... I> | |
constexpr void tuple_for_each_impl(Tuple& t, Fn& f, std::index_sequence<I...>) { | |
(f(std::get<I>(std::forward<Tuple>(t))), ...); | |
} | |
template <class Tuple, class Fn> | |
constexpr void tuple_for_each(Tuple& t, Fn& f) { | |
tuple_for_each_impl(t, f, | |
std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{} | |
); | |
} | |
template<class Struct, class Tuple, std::size_t... I> | |
constexpr Struct tuple_to_struct_impl(Tuple&& t, std::index_sequence<I...>) { | |
return {std::get<I>(std::forward<Tuple>(t))...}; | |
} | |
template<class Struct, class Tuple> | |
constexpr Struct tuple_to_struct(Tuple&& t) { | |
return tuple_to_struct_impl<Struct>( | |
std::forward<Tuple>(t), | |
std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{} | |
); | |
} | |
union N32 { | |
uint32_t u32; | |
int32_t i32; | |
uint16_t u16[2]; | |
int16_t i16[2]; | |
uint8_t u8[4]; | |
int8_t i8[4]; | |
float f; | |
}; | |
struct Serializer { | |
std::vector<uint8_t> buffer; | |
public: | |
template <typename... Args> | |
inline void operator () (const std::tuple<Args...>& tuple) { | |
Serializer serializer; | |
tuple_for_each(tuple, serializer); | |
buffer.insert(buffer.end(), serializer.buffer.begin(), serializer.buffer.end()); | |
} | |
inline void operator () (uint8_t val) { | |
buffer.push_back(val); | |
} | |
inline void operator () (uint16_t val) { | |
N32 n; | |
n.u16[0] = htons(val); | |
buffer.push_back(n.u8[0]); | |
buffer.push_back(n.u8[1]); | |
} | |
inline void operator () (uint32_t val) { | |
N32 n; | |
n.u32 = htonl(val); | |
buffer.push_back(n.u8[0]); | |
buffer.push_back(n.u8[1]); | |
buffer.push_back(n.u8[2]); | |
buffer.push_back(n.u8[3]); | |
} | |
inline void operator () (const std::string& val) { | |
for (char c : val) { | |
buffer.push_back(c); | |
} | |
buffer.push_back(0); | |
} | |
template <typename T> | |
inline void operator () (const std::vector<T>& val) { | |
(*this)((uint32_t) val.size()); | |
for (const auto& e : val) { | |
(*this)(e); | |
} | |
} | |
template <typename K, typename V> | |
inline void operator () (const std::map<K, V>& val) { | |
(*this)((uint32_t) val.size()); | |
for (const auto& p : val) { | |
(*this)(p.first); | |
(*this)(p.second); | |
} | |
} | |
template <typename T> | |
inline void operator () (const T& object) { | |
(*this)(object.toTuple()); | |
} | |
}; | |
template <typename T> | |
inline std::vector<uint8_t> serializeObject(const T& object) { | |
Serializer serializer; | |
auto tuple = object.toTuple(); | |
tuple_for_each(tuple, serializer); | |
return serializer.buffer; | |
} | |
template <typename T> | |
inline std::vector<uint8_t> serialize(const T& object, uint32_t magic, uint32_t objectVersion) { | |
std::vector<uint8_t> data = serializeObject(object); | |
std::vector<uint8_t> header; | |
N32 n; | |
auto push_u32 = [&n, &header] { | |
for (int i = 0; i < 4; i++) | |
header.push_back(n.u8[i]); | |
}; | |
n.u32 = htonl(magic); | |
push_u32(); | |
n.u32 = htonl(SERIALIZER_VERSION); | |
push_u32(); | |
n.u32 = htonl(objectVersion); | |
push_u32(); | |
header.insert(header.end(), data.begin(), data.end()); | |
return header; | |
} | |
template <typename T> | |
inline int serialize(const std::string& filename, const T& object, uint32_t magic, uint32_t objectVersion) { | |
std::vector<uint8_t> data = serialize(object, magic, objectVersion); | |
FILE* file = fopen(filename.c_str(), "wb"); | |
if (!file) { | |
return 1; | |
} | |
fwrite(data.data(), 1, data.size(), file); | |
fclose(file); | |
return 0; | |
} | |
struct Deserializer { | |
std::vector<uint8_t>& buffer; | |
size_t offset = 0; | |
public: | |
inline Deserializer(std::vector<uint8_t>& buffer, size_t offset = 0) : buffer(buffer), offset(offset) {} | |
inline void operator () (uint8_t& val) { | |
val = buffer[offset]; | |
offset++; | |
} | |
inline void operator () (uint16_t& val) { | |
N32 n; | |
n.u8[0] = buffer[offset++]; | |
n.u8[1] = buffer[offset++]; | |
val = ntohs(n.u16[0]); | |
} | |
inline void operator () (uint32_t& val) { | |
N32 n; | |
n.u8[0] = buffer[offset++]; | |
n.u8[1] = buffer[offset++]; | |
n.u8[2] = buffer[offset++]; | |
n.u8[3] = buffer[offset++]; | |
val = ntohl(n.u32); | |
} | |
inline void operator () (std::string& val) { | |
while (buffer[offset] != 0) { | |
val.push_back(buffer[offset++]); | |
} | |
offset++; | |
} | |
template <typename T> | |
inline void operator () (std::vector<T>& val) { | |
uint32_t size = 0; | |
(*this)(size); | |
for (uint32_t i = 0; i < size; i++) { | |
T e; | |
(*this)(e); | |
val.push_back(e); | |
} | |
} | |
template <typename K, typename V> | |
inline void operator () (std::map<K, V>& val) { | |
uint32_t size = 0; | |
(*this)(size); | |
for (uint32_t i = 0; i < size; i++) { | |
K k; V v; | |
(*this)(k); | |
(*this)(v); | |
val[k] = v; | |
} | |
} | |
template <typename T> | |
inline void operator () (T& object) { | |
auto tuple = object.toRefTuple(); | |
Deserializer deserializer(buffer, offset); | |
tuple_for_each(tuple, deserializer); | |
object = tuple_to_struct<T>(tuple); | |
offset = deserializer.offset; | |
} | |
}; | |
template <typename T> | |
inline void deserializeObject(std::vector<uint8_t> buffer, T& object) { | |
Deserializer deserializer(buffer); | |
auto tuple = object.toRefTuple(); | |
tuple_for_each(tuple, deserializer); | |
object = tuple_to_struct<T>(tuple); | |
} | |
template <typename T> | |
inline int deserialize(const std::vector<uint8_t>& bytes, T& object, uint32_t& magic, uint32_t& objectVersion) { | |
N32 n; | |
size_t offset = 0; | |
auto load_u32 = [&n, &bytes, &offset] { | |
for (int i = 0; i < 4; i++) | |
n.u8[i] = bytes[offset++]; | |
}; | |
load_u32(); | |
magic = ntohl(n.u32); | |
load_u32(); | |
uint32_t serializerVersion = ntohl(n.u32); | |
load_u32(); | |
objectVersion = ntohl(n.u32); | |
if (serializerVersion != SERIALIZER_VERSION) { | |
return 3; | |
} | |
std::vector<uint8_t> objectData = std::vector(bytes.begin() + sizeof(uint32_t) * 3, bytes.end()); | |
deserializeObject(objectData, object); | |
return 0; | |
} | |
template <typename T> | |
inline int deserialize(const std::string& filename, T& object, uint32_t& magic, uint32_t& objectVersion) { | |
FILE* file = fopen(filename.c_str(), "rb"); | |
if (!file) { | |
return 1; | |
} | |
fseek(file, 0, SEEK_END); | |
size_t size = ftell(file); | |
rewind(file); | |
uint8_t* data = new uint8_t[size]; | |
if (fread(data, 1, size, file) != size) { | |
delete [] data; | |
fclose(file); | |
return 2; | |
} | |
std::vector<uint8_t> buffer(data, data + size); | |
int ret = deserialize(buffer, object, magic, objectVersion); | |
delete [] data; | |
fclose(file); | |
return ret; | |
} | |
} /* namespace mrt */ | |
#endif /* _MRT_SERIALIZATION_H_ */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment