Skip to content

Commit

Permalink
fix: referential equalities should ignore child nodes (#244)
Browse files Browse the repository at this point in the history
* add failing test

* add failing test

* tweak

* fix test

* Update src/plainer.ts

* Update src/plainer.ts

* less diff
  • Loading branch information
KATT authored Jul 14, 2023
1 parent 671866a commit 601fc26
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 30 deletions.
31 changes: 31 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1126,3 +1126,34 @@ test('superjson instances are independent of one another', () => {
const res2 = s2.serialize(value);
expect(res2.json).toEqual(value);
});

test('regression #245: superjson referential equalities only use the top-most parent node', () => {
type Node = {
children: Node[];
};
const root: Node = {
children: [],
};
const input = {
a: root,
b: root,
};
const res = SuperJSON.serialize(input);

expect(res.meta?.referentialEqualities).toHaveProperty(['a']);

// saying that a.children is equal to b.children is redundant since its already know that a === b
expect(res.meta?.referentialEqualities).not.toHaveProperty(['a.children']);
expect(res.meta).toMatchInlineSnapshot(`
Object {
"referentialEqualities": Object {
"a": Array [
"b",
],
},
}
`);

const parsed = SuperJSON.deserialize(res);
expect(parsed).toEqual(input);
});
73 changes: 43 additions & 30 deletions src/plainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,27 +155,40 @@ export const walker = (
identities: Map<any, any[][]>,
superJson: SuperJSON,
path: any[] = [],
objectsInThisPath: any[] = []
objectsInThisPath: any[] = [],
seenObjects = new Map<unknown, Result>()
): Result => {
if (!isPrimitive(object)) {
const primitive = isPrimitive(object);

if (!primitive) {
addIdentity(object, path, identities);

const seen = seenObjects.get(object);
if (seen) {
// short-circuit result if we've seen this object before
return seen;
}
}

if (!isDeep(object, superJson)) {
const transformed = transformValue(object, superJson);
if (transformed) {
return {
transformedValue: transformed.value,
annotations: [transformed.type],
};
} else {
return {
transformedValue: object,
};

const result: Result = transformed
? {
transformedValue: transformed.value,
annotations: [transformed.type],
}
: {
transformedValue: object,
};
if (!primitive) {
seenObjects.set(object, result);
}
return result;
}

if (includes(objectsInThisPath, object)) {
// prevent circular references
return {
transformedValue: null,
};
Expand All @@ -184,10 +197,6 @@ export const walker = (
const transformationResult = transformValue(object, superJson);
const transformed = transformationResult?.value ?? object;

if (!isPrimitive(object)) {
objectsInThisPath = [...objectsInThisPath, object];
}

const transformedValue: any = isArray(transformed) ? [] : {};
const innerAnnotations: Record<string, Tree<TypeAnnotation>> = {};

Expand All @@ -197,7 +206,8 @@ export const walker = (
identities,
superJson,
[...path, index],
objectsInThisPath
[...objectsInThisPath, object],
seenObjects
);

transformedValue[index] = recursiveResult.transformedValue;
Expand All @@ -211,19 +221,22 @@ export const walker = (
}
});

if (isEmptyObject(innerAnnotations)) {
return {
transformedValue,
annotations: !!transformationResult
? [transformationResult.type]
: undefined,
};
} else {
return {
transformedValue,
annotations: !!transformationResult
? [transformationResult.type, innerAnnotations]
: innerAnnotations,
};
const result: Result = isEmptyObject(innerAnnotations)
? {
transformedValue,
annotations: !!transformationResult
? [transformationResult.type]
: undefined,
}
: {
transformedValue,
annotations: !!transformationResult
? [transformationResult.type, innerAnnotations]
: innerAnnotations,
};
if (!primitive) {
seenObjects.set(object, result);
}

return result;
};

0 comments on commit 601fc26

Please sign in to comment.