Implementing serialisation with minmal boilerplate and template overhead using core.reflect

Stefan Koch uplink.coder at googlemail.com
Sun Aug 15 11:18:57 UTC 2021


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.

```D
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


More information about the Digitalmars-d mailing list