Skip to content

Instantly share code, notes, and snippets.

@SeijiEmery
Last active April 7, 2019 21:51
Show Gist options
  • Save SeijiEmery/20d71ca127b11c7f214e042a7bc8ff69 to your computer and use it in GitHub Desktop.
Save SeijiEmery/20d71ca127b11c7f214e042a7bc8ff69 to your computer and use it in GitHub Desktop.
Full serialization lib in 160 lines of code (D)
chmod +x serializer.d
./serializer.d
Initial thing: MyComplexDataType([Thing("foo", 10, 12.4), Thing("bar", 4, 22.9), Thing("baz", 19, 14.5)], "fubar")
Restored thing: MyComplexDataType([Thing("foo", 10, 12.4), Thing("bar", 4, 22.9), Thing("baz", 19, 14.5)], "fubar")
data (82 bytes): [3, 0, 0, 0, 0, 0, 0, 0, 102, 111, 111, 10, 0, 0, 0, 205, 204, 204, 204, 204, 204, 40, 64, 3, 0, 0, 0, 0, 0, 0, 0, 98, 97, 114, 4, 0, 0, 0, 102, 102, 102, 102, 102, 230, 54, 64, 3, 0, 0, 0, 0, 0, 0, 0, 98, 97, 122, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 64, 5, 0, 0, 0, 0, 0, 0, 0, 102, 117, 98, 97, 114]
#!/usr/bin/env rdmd
import std.traits: hasIndirections, isBasicType;
import std.stdio: writefln;
//
// Our serialization interface
//
// Serialize an object / value to a stream of bytes
// usage:
// auto data = serialize(x);
//
ubyte[] serialize (T)(T object) {
ubyte[] data;
serialize!write_bytes(data, object);
return data;
}
// Deserialize a serialized stream of bytes back into an existing object / value
// usage:
// deserialize(x, data);
//
auto ref deserialize (T)(T object, ubyte[] data) {
serialize!read_bytes(data, object);
return object;
}
// Deserialize a stream of bytes and construct / create a new object or value of type T
// usage:
// auto x = deserialize!Thing(data);
//
auto deserialize (T)(ubyte[] data)
{
static if (is(T == class)) {
T object = new T();
} else {
T object;
}
return deserialize(object, data);
}
//
// Implementation details
//
// Basic serialization functions:
// write a stream of bytes to a stream of bytes
// or
// read a stream of bytes from a stream of bytes
void write_bytes (ref ubyte[] data, const ubyte[] memory) {
data ~= memory;
}
void read_bytes (ref ubyte[] data, ref ubyte[] memory) {
memory[0..$] = data[0..memory.length];
data = data[memory.length..$];
}
//
// serialize() is a generic serialization / deserialization function with the following type signature:
//
// serialize!(serializer_function)(data, thing-to-serialize-or-deserialize)
//
// Most basic case: forward to the serialization function
void serialize (alias serializer)(ref ubyte[] data, ref ubyte[] memory) {
serializer(data, memory);
}
// Best case: value type without pointers or references
// (can cast this directly to a stream of bytes and write that)
void serialize (alias serializer, T)(ref ubyte[] data, T value)
if (!hasIndirections!T || isBasicType!T)
{
auto value_data = cast(ubyte[])((&value)[0..1]);
serializer(data, value_data);
}
// Even better case: array of value types without pointers or references
// (can cast this directly to a stream of bytes and write that)
void serialize (alias serializer, T)(ref ubyte[] data, T[] values)
if (!hasIndirections!T || isBasicType!T || is(T[] == string))
{
serialize!serializer(data, values.length);
auto value_data = cast(ubyte[])values;
serializer(data, value_data);
}
// Array of non-value types: iterate over elements and serialize each individually
void serialize (alias serializer, T)(ref ubyte[] data, T[] values)
if (hasIndirections!T && __traits(compiles, serialize!serializer(data, values[0])))
{
foreach (value; values) {
serialize!serializer(data, value);
}
}
// Struct / class with pointers / references:
// (can't read/write as byte stream; iterate over fields and serialize each of those in order)
void serialize (alias serializer, T)(ref ubyte[] data, T value)
if (hasIndirections!T && (is(T == struct) || is(T == class)))
{
foreach (ref field; value.tupleof) {
serialize!serializer(data, field);
}
}
//Custom serialization method? call that
void serialize (alias serializer, T)(ref ubyte[] data, T object)
if (__traits(compiles, object.serialize!serializer(data)))
{
object.serialize!serializer(data);
}
// Helper function: lets you call
// serialize!serializer(data, thing1, thing2, thing3, ...);
// for custom serialization methods
//
void serialize (alias serializer, Args...)(ref ubyte[] data, Args args)
if (args.length >= 2)
{
//writefln("serializing multiple things");
foreach (arg; args) {
serialize!serializer(data, arg);
}
}
//
// Example :)
//
struct Thing {
string x;
int y;
double z;
}
struct MyComplexDataType {
Thing[] things;
string x;
}
int main () {
//ubyte[] data;
//auto thing = "fubar";
//auto thing = Thing("foo", 1, 2);
//auto thing = [ Thing("foo", 1, 2), Thing("bar", 2, 12) ];
auto thing = MyComplexDataType(
[
Thing("foo", 10, 12.4),
Thing("bar", 4, 22.9),
Thing("baz", 19, 14.5),
],
"fubar"
);
alias T = typeof(thing);
writefln("Initial thing: %s", thing);
auto data = serialize(thing);
auto restoredThing = deserialize!T(data);
writefln("Restored thing: %s", thing);
writefln("data (%s bytes): %s", data.length, data);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment