From dd3aeb51cf15bd8d7c6e708395d844a385c8713a Mon Sep 17 00:00:00 2001 From: Alex / KATT Date: Mon, 17 Jul 2023 10:17:46 +0200 Subject: [PATCH] feat: add `dedupe` flag (#247) * add failing test * add failing test * tweak * fix test * Update src/plainer.ts * Update src/plainer.ts * less diff * feat: add dedupe flag * rm useless test * refactor: name dedupeReferentialEqualities * fix: dedupe is enough * fix: test * fix: test --------- Co-authored-by: Simon Knott --- src/index.test.ts | 36 ++++++++++++++++++++++++++++++++++++ src/index.ts | 22 +++++++++++++++++++--- src/plainer.spec.ts | 3 ++- src/plainer.ts | 8 +++++++- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index fa5341f..566d80f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1157,3 +1157,39 @@ test('regression #245: superjson referential equalities only use the top-most pa const parsed = SuperJSON.deserialize(res); expect(parsed).toEqual(input); }); + +test('dedupe=true', () => { + const instance = new SuperJSON({ + dedupe: true, + }); + + type Node = { + children: Node[]; + }; + const root: Node = { + children: [], + }; + const input = { + a: root, + b: root, + }; + const output = instance.serialize(input); + + const json = output.json as any; + + expect(json.a); + + // This has already been seen and should be deduped + expect(json.b).toBeNull(); + + expect(json).toMatchInlineSnapshot(` + Object { + "a": Object { + "children": Array [], + }, + "b": null, + } + `); + + expect(instance.deserialize(output)).toEqual(input); +}); diff --git a/src/index.ts b/src/index.ts index 5103c66..320caa8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { SuperJSONResult, SuperJSONValue, Class, JSONValue } from './types'; +import { Class, JSONValue, SuperJSONResult, SuperJSONValue } from './types'; import { ClassRegistry, RegisterOptions } from './class-registry'; import { Registry } from './registry'; import { @@ -6,17 +6,33 @@ import { CustomTransformerRegistry, } from './custom-transformer-registry'; import { - walker, applyReferentialEqualityAnnotations, applyValueAnnotations, generateReferentialEqualityAnnotations, + walker, } from './plainer'; import { copy } from 'copy-anything'; export default class SuperJSON { + /** + * If true, SuperJSON will make sure only one instance of referentially equal objects are serialized and the rest are replaced with `null`. + */ + private readonly dedupe: boolean; + + /** + * @param dedupeReferentialEqualities If true, SuperJSON will make sure only one instance of referentially equal objects are serialized and the rest are replaced with `null`. + */ + constructor({ + dedupe = false, + }: { + dedupe?: boolean; + } = {}) { + this.dedupe = dedupe; + } + serialize(object: SuperJSONValue): SuperJSONResult { const identities = new Map(); - const output = walker(object, identities, this); + const output = walker(object, identities, this, this.dedupe); const res: SuperJSONResult = { json: output.transformedValue, }; diff --git a/src/plainer.spec.ts b/src/plainer.spec.ts index be11cd3..47db3b2 100644 --- a/src/plainer.spec.ts +++ b/src/plainer.spec.ts @@ -9,7 +9,8 @@ test('walker', () => { b: /test/g, }, new Map(), - new SuperJSON() + new SuperJSON(), + false ) ).toEqual({ transformedValue: { diff --git a/src/plainer.ts b/src/plainer.ts index 4b4cc42..1a568df 100644 --- a/src/plainer.ts +++ b/src/plainer.ts @@ -154,6 +154,7 @@ export const walker = ( object: any, identities: Map, superJson: SuperJSON, + dedupe: boolean, path: any[] = [], objectsInThisPath: any[] = [], seenObjects = new Map() @@ -166,7 +167,11 @@ export const walker = ( const seen = seenObjects.get(object); if (seen) { // short-circuit result if we've seen this object before - return seen; + return dedupe + ? { + transformedValue: null, + } + : seen; } } @@ -205,6 +210,7 @@ export const walker = ( value, identities, superJson, + dedupe, [...path, index], [...objectsInThisPath, object], seenObjects