From 1e8d61d38e7310360c834605887c96fb33d0d4ac Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Wed, 29 May 2024 03:36:28 +0200 Subject: [PATCH] feat(type): automatically assign .path to SerializationError in TemplateState.convert() errors --- .../src/app/pages/documentation.component.ts | 1 + .../documentation/runtime-types/extend.md | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 website/src/pages/documentation/runtime-types/extend.md diff --git a/website/src/app/pages/documentation.component.ts b/website/src/app/pages/documentation.component.ts index 8047a786b..a02a8f88e 100644 --- a/website/src/app/pages/documentation.component.ts +++ b/website/src/app/pages/documentation.component.ts @@ -76,6 +76,7 @@ import { PlatformHelper } from '@app/app/utils'; Reflection Serialization Validation + Extend Custom serializer External Types Bytecode diff --git a/website/src/pages/documentation/runtime-types/extend.md b/website/src/pages/documentation/runtime-types/extend.md new file mode 100644 index 000000000..d66271d47 --- /dev/null +++ b/website/src/pages/documentation/runtime-types/extend.md @@ -0,0 +1,56 @@ +# Extend + +## Custom Serialization + +You can extend the serialization of a type by defining either write your own `Serializer` or extend the default `serializer`. + +This example shows how to serialize and deserialize a class `Point` to a tuple `[number, number]`. + +```typescript +import { serializer, SerializationError } from '@deepkit/type'; + +class Point { + constructor(public x: number, public y: number) { + } +} + +// deserialize means from JSON to (class) instance. +serializer.deserializeRegistry.registerClass(Point, (type, state) => { + state.convert((v: any) => { + // if it's already a Point instance, we are done + if (v instanceof Point) return v; + + // at this point `v` could be anything (except undefined), so we need to check + if (!Array.isArray(v)) throw new SerializationError('Expected array'); + if (v.length !== 2) throw new SerializationError('Expected array with two elements'); + if (typeof v[0] !== 'number' || typeof v[1] !== 'number') throw new SerializationError('Expected array with two numbers'); + return new Point(v[0], v[1]); + }); +}); + +serializer.serializeRegistry.registerClass(Point, (type, state) => { + state.convert((v: Point) => { + // at this point `v` is always a Point instance + return [v.x, v.y]; + }); +}); + +// cast and deserialize use `serializer` by default +const point = cast([1, 2], undefined, serializer); +expect(point).toBeInstanceOf(Point); +expect(point.x).toBe(1); +expect(point.y).toBe(2); + +{ + expect(() => deserialize(['vbb'])).toThrowError(SerializationError); + expect(() => deserialize(['vbb'])).toThrow('Expected array with two elements') +} + +// serialize uses `serializer` by default +const json = serialize(point); +expect(json).toEqual([1, 2]); +``` + +Please note that this is only working for the regular `@deepkit/type` functions like `cast`, `deserialize`, and `serialize`. + +This won't be transferred to the database layer, since the database layer uses the types as defined in the Entity class for migration and serialization (e.g. BSON serialization).