Good Day everyone,
A friend asked me recently of core.reflect could be used for serialization.
The code to do that generically is about 100 lines of code.
Since it's so little I am just going to post it here verbatim.
class C { @("NoSerialize") ubyte a; uint b; @("NoSerialize") ulong c;
this(ubyte a, uint b, ulong c)
{
this.a = a; this.b = b; this.c = c;
}
}
struct S { @("NoSerialize") ubyte a; uint b; ubyte c; @("NoSerialize") ulong d; }
import core.reflect.reflect;
/// if a type is an aggregate type return the aggregate declaration
/// otherwise return null
AggregateDeclaration aggregateFromType(const Type type)
{
AggregateDeclaration result = null;
if (auto ts = cast(TypeStruct)type)
{
result = ts.sym;
} else if (auto tc = cast(TypeClass)type)
{
result = tc.sym;
}
return result;
}
bool hasNoSerializeAttrib(const Declaration d)
{
bool NoSerialize = false;
foreach(attr;d.attributes)
{
auto se = cast(StringLiteral)attr;
if (se && se.string_ == "NoSerialize")
{
NoSerialize = true;
break;
}
}
return NoSerialize;
}
/// the only template in here. only one instances per serialzed-root-type
const(ubyte[]) serialize(T)(T value) {
static immutable Type type = cast(immutable Type) nodeFromName("T");
return serializeType(cast(ubyte*)&value, type);
}
const(ubyte[]) serializeType(const ubyte* ptr, const Type type)
{
// writeShallowTypeDescriptor(Type);
// printf("Serializing type: %s\n", type.identifier.ptr);
if (auto sa = cast (TypeArray) type)
{
ulong length = sa.dim;
const void* values = ptr;
auto elemType = sa.nextOf;
return serializeArray(length, ptr, elemType);
}
else if (auto da = cast(TypeSlice) type)
{
ulong length = *cast(size_t*) ptr;
const void* values = *cast(const ubyte**)(ptr + size_t.sizeof);
auto elemType = da.nextOf;
return serializeArray(length, ptr, elemType);
}
else if (auto ts = cast(TypeStruct) type)
{
auto fields = ts.sym.fields;
return serializeAggregate(ptr, fields);
}
else if (auto tc = cast(TypeClass) type)
{
auto fields = tc.sym.fields;
return serializeAggregate(*cast(const ubyte**)ptr, fields);
}
else if (auto tb = cast(TypeBasic) type)
{
// writeTypeTag ?
return ptr[0 .. type.size];
}
else assert(0, "Serialisation for " ~ type.identifier ~ " not implemented .. " ~ (cast()type).toString());
}
ubyte[] serializeArray(ulong length, const ubyte* ptr, const Type elemType)
{
ubyte[] result;
// writeLength(length)
foreach(i; 0 .. length)
{
result ~= serializeType(ptr, elemType);
}
return result;
}
ubyte[] serializeAggregate(const ubyte* ptr, VariableDeclaration[] fields)
{
ubyte[] result;
foreach(f; fields)
{
if (hasNoSerializeAttrib(f))
{
// skip fields which are annotated with noSerialize;
continue;
}
result ~= serializeType(ptr + f.offset, f.type);
}
return result;
}
void main()
{
S s = S(72, 19992034, 98);
C c = new C(72, 19992039, 98);
auto buffer = serialize(s);
assert(buffer.length == 5);
assert ((buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24) == 19992034);
assert (buffer[4] == 98);
auto buffer_c = serialize(c);
assert(buffer_c.length == 4);
assert ((buffer_c[0] | buffer_c[1] << 8 | buffer_c[2] << 16 | buffer_c[3] << 24) == 19992039);
// we can see the fields annotated with @("NoSerialize") are skipped
}
I would like to know what you think about this
In my next example I will show how you can modify serialization for library types source-code you can't control.
However for that to work core.reflect
needs to be extended a little ;)
Cheers and have a nice day,
Stefan