Skip to content

Instantly share code, notes, and snippets.

@maxrt101
Last active September 29, 2023 00:25
Show Gist options
  • Save maxrt101/e8ae805dfab72abb348a463ae133c063 to your computer and use it in GitHub Desktop.
Save maxrt101/e8ae805dfab72abb348a463ae133c063 to your computer and use it in GitHub Desktop.
Binary Serializer/Deserializer for C++ structs
/* 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