-
Notifications
You must be signed in to change notification settings - Fork 112
Description
TLDR: With a custom class serializer, nested fields are not serialized but copied as-is.
I have a custom class Foo that should be serialized. The class can look like this:
class Foo {
bar: any;
constructor(bar) {
this.bar = bar;
}
}foo.bar can be anything, including a nested Map that can even contain nested instances of Foo somewhere deep.
If I define a custom transformer like this:
serializer.registerCustom(
{
isApplicable(v: unknown): v is Foo {
return v instanceof Foo;
},
serialize(foo) {
return { bar: foo.bar };
},
deserialize(data) {
return new Foo(data);
},
},
"Foo",
);It doesn't work as expected, as the serializer 'stops' at bar and simply passes it without performing nested serialization.
I am not sure how to tell SuperJson to keep going into foo and serialize everything using all existing rules, adding all nested fields to the result meta for nested types or circular references.
Is it possible?
The expected result would be:
const json = new SuperJson();
// register my custom rule (serializer.registerCustom(...))
const nestedFoo = new Foo(0);
const someMap = new Map();
someMap.set("nested", nestedFoo);
const foo2 = new Foo(someMap);
const serialized = json.serialize(foo2);
const parsed = json.parse(serialized);
const nestedFooClone = parsed.bar.get("nested");
nestedFooClone.foo === 0; // true - they are equal
nestedFooClone === nestedFoo; // false - they are not the same instance, i.e., they are clonedNote 2: I've tried with serializer.registerClass - the result is the same (I did check the source code of SuperJson):
const allowedProps = superJson.classRegistry.getAllowedProps(clazz.constructor);
if (!allowedProps) {
return { ...clazz };
}
const result = {};
allowedProps.forEach(prop => {
result[prop] = clazz[prop];
});As seen above, class properties are shallowly passed over without nested serialization.
Note 3: If in the case above foo would include some circular references, the serialize result would also have them, and trying to stringify will throw an error.
Possible solution:
Possible API could look like:
serializer.registerCustom(
{
isApplicable(v: unknown): v is Foo {
return v instanceof Foo;
},
serialize(foo, serialize) {
return { bar: serialize(foo.bar) }; // <-------- Here! We perform nested serialization. It would keep serializing and adding to `meta` inside superjson result
},
deserialize(data, deserialize) {
return new Foo(deserialize(data)); // <----- Also here, before we create the instance, we de-serialize first (possibly this could be done automatically)
},
},
"Foo",
);Or:
serializer.registerCustom would have another option isNested: boolean. If enabled, serialization output is treated as deep and traversed
Some workaround or solution would be very welcome. I am creating a generic coder/decoder that can have arbitral, possibly nested and circular data structures built using custom classes. Not being able to serialize nested properties is quite a blocker for it to work