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