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

Jacob Carlborg doob at me.com
Tue Dec 3 09:47:44 UTC 2019


On Tuesday, 12 November 2019 at 09:15:28 UTC, SealabJaster wrote:

> 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.

They way I handled this in Orange [1] which allows to 
(de)serialize third party types which you don't have any control 
of. First, structs have the same problem as classes: there can be 
private members. They way this is handled is to use `.tupleof`. 
`.tupleof` will bypass the protection and allows to read and 
write private fields:

module foo;

class Foo
{
     private int a;
     int getA() { return a; }
}

module main;

import std.stdio;
import foo;

void main()
{
     auto foo = new Foo;
     foo.tupleof[0] = 3;
     assert(foo.getA == 3);
     assert(foo.tupleof[0] == 3);
}

`.tupleof` is used both for serialization and deserialization. To 
create an instance of a class use the same way as the runtime 
does. This function [2] is called by the compiler when the code 
contains "new Object" (extracted to Orange here [3]). This will 
not call a constructor, but that's fine since we will set the 
fields anyway directly after.

Orange also offers a way to tweak the (de)serialization process 
with hooks. You can define methods in your structs or classes 
with any of the following UDAs: `onSerializing`, `onSerialized`, 
`onDeserializing` and `onDeserialized`. These method will be 
called (if they exist) before/after the (de)serialization process 
[4]. This allows you do preform some post-processing that might 
be needed since no constructor has been called on deserialization.

When it comes to (de)serializing derived types through a base 
class reference Orange requires you do register all derived types 
[5].

Then there's the problem with non-mutable fields and instances as 
well. But you can just cast away those when deserializing.

There's also the issue with circular references [6]. But that's 
pretty easy to solve by storing  all serialized instances to see 
if they have already been serialized or not.

[1] http://github.com/jacob-carlborg/orange

[2] 
https://github.com/dlang/druntime/blob/873fac33014c5af680c4bed69bb74cb0f192198a/src/rt/lifetime.d#L73

[3] 
https://github.com/jacob-carlborg/orange/blob/90f1dbb0097ba4a319805bfb7d109f7038418ac6/orange/util/Reflection.d#L149-L183

[4] 
https://github.com/jacob-carlborg/orange/blob/master/tests/Events.d

[5] 
https://github.com/jacob-carlborg/orange/blob/90f1dbb0097ba4a319805bfb7d109f7038418ac6/orange/serialization/Serializer.d#L241-L262

[6] 
https://github.com/jacob-carlborg/orange/blob/master/tests/CircularReference.d

--
/Jacob Carlborg


More information about the Digitalmars-d-announce mailing list