Blog series to teach and show off D's metaprogramming by creating a JSON serialiser

Steven Schveighoffer schveiguy at gmail.com
Tue Nov 12 15:30:30 UTC 2019


On 11/12/19 4:15 AM, SealabJaster wrote:
> On Monday, 11 November 2019 at 16:56:31 UTC, Steven Schveighoffer wrote:
>> The tough part about serializing classes is if the class is not final, 
>> how do you serialize the derived data. It requires some sort of user 
>> help to
>> tell it how to get at the data.
> 
> And if you do allow things such as letting classes have a 'deserialise' 
> member function which can be overloaded, you still need to create or be 
> given an instance of the class beforehand, which brings things back 
> around to the "constructor issue" in the latest post.
> 
> I don't quite know if there's actually an automatic solution for classes 
> that covers most cases? Anything I can think of places limitations in 
> some form or another.

There's definitely not an automatic solution. However, in my jsoniopipe 
code, the base class can have a static function fromJSON that 
initializes the requested type. Such a thing has to be a member, but in 
theory could just be a registered callback.

In my use case, I have a base class and all the derivatives in the same 
module (they are just messages). Here is some code that returns the base 
type that is portable to any such situation:

     static Base fromJSON(JT)(ref JT tokenizer, ReleasePolicy relPol)
     {
         // this creates a rewind point, so I can go back and parse the
         // object after finding the type.
         tokenizer.startCache();
         if(tokenizer.parseTo("type")) // this part depends on the encoding
         {
             string t;
             // get the type name
             tokenizer.deserialize(t);
             import std.meta;
             // alias to the module for Base, which also contains all the
             // derivatives
             alias mod = __traits(parent, Base);
             enum isBase(string s) =
                 is(__traits(getMember, mod, s) : Base) &&
                 !is(__traits(getMember, mod, s) == Base);

             // this gets all the classes in this module that are
             // derivatives of Base (except Base). D is so cool ;)
             alias Derivatives = Filter!(isBase, __traits(allMembers, mod));
             // reset the tokenizer to read the whole JSON data for this 
object
             tokenizer.rewind();
             tokenizer.endCache();
             switch(t)
             {
                 static foreach(typename; Derivatives)
                 {
                 // Note: MyType is the JSON string that designates the
                 // type, your implementation may vary.
                 case __traits(getMember, mod, typename).MyType:
                     {
                         auto result = new __traits(getMember, mod, 
typename);
                         // defined by the library, does a rote
                         // serialization of members.
                         tokenizer.deserializeAllMembers(result, relPol);
                         return result;
                     }
                 }
             default:
                 assert(false, "Unknown type identifier: " ~ t);
             }
         }
         assert(false, "Couldn't find type identifier");
     }

Basically, I never have to touch this again, thanks D. Just add a new 
type that derives from Base with a proper MyType member, and it gets 
added to the list. It just has to be added to the module itself. If your 
class hierarchy is spread out in different files, you can make some kind 
of registration scheme to handle the deserialization.

-Steve


More information about the Digitalmars-d-announce mailing list