Skip to content

Commit 36faa6a

Browse files
committed
fix: require an identity function when deserializing a graph whose nodes are objects
1 parent 9c99039 commit 36faa6a

2 files changed

Lines changed: 60 additions & 10 deletions

File tree

src/utils/deserializeGraph.spec.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,66 @@
1-
import { describe, expectTypeOf, it } from 'vitest';
1+
import { describe, expectTypeOf, it, expect } from 'vitest';
22
import { Graph } from '../Graph.js';
33
import { checkSerialized } from '../test-utils.js';
4+
import { Serialized } from '../types.js';
45
import { deserializeGraph } from './deserializeGraph.js';
56
import { serializeGraph } from './serializeGraph.js';
67

78
describe('serializeGraph', () => {
8-
it('Should deserialize a graph.', function () {
9+
it('should deserialize a graph', () => {
910
const g = new Graph<string>().addEdge('a', 'b').addEdge('b', 'c');
1011
const serialized = serializeGraph(g);
1112

1213
const graph = deserializeGraph(serialized);
1314
checkSerialized(serializeGraph(graph));
1415
});
1516

16-
it('Should deserialize a graph passed to constructor.', function () {
17+
it('should deserialize a graph passed to constructor', () => {
1718
const g = new Graph<string>().addEdge('a', 'b').addEdge('b', 'c');
1819
const serialized = serializeGraph(g);
1920
const graph = new Graph(serialized);
2021
checkSerialized(serializeGraph(graph));
2122
});
2223

23-
it.skip('should return a graph with type inferred from the input', function () {
24-
const nodeA = { title: 'a' };
25-
const nodeB = { title: 'b' };
24+
it('should not duplicate nodes when they are objects', () => {
25+
const nodeA = { id: 1, title: 'a' };
26+
const nodeB = { id: 2, title: 'b' };
27+
28+
const stringData = JSON.stringify({
29+
nodes: [nodeA, nodeB],
30+
links: [{ source: nodeA, target: nodeB, props: { type: 'foo' } }],
31+
});
32+
33+
const serializedGraph: Serialized<{ id: number; title: string }, { type: string }> =
34+
JSON.parse(stringData);
35+
36+
const graph = deserializeGraph(serializedGraph, (n) => n.id);
37+
38+
expect(graph.nodes).toHaveLength(2);
39+
});
40+
41+
it.skip('should return a graph with type inferred from the input', () => {
42+
const nodeA = { id: 1, title: 'a' };
43+
const nodeB = { id: 2, title: 'b' };
44+
const serialized = {
45+
nodes: [nodeA, nodeB],
46+
links: [{ source: nodeA, target: nodeB, props: { type: 'foo' } }],
47+
};
48+
49+
const graph = deserializeGraph(serialized, (n) => n.id);
50+
expectTypeOf(graph).toEqualTypeOf<
51+
Graph<{ id: number; title: string }, { type: string }>
52+
>();
53+
});
54+
55+
it.skip('should require an identity function when nodes are objects', () => {
56+
const nodeA = { id: 1, title: 'a' };
57+
const nodeB = { id: 2, title: 'b' };
2658
const serialized = {
2759
nodes: [nodeA, nodeB],
2860
links: [{ source: nodeA, target: nodeB, props: { type: 'foo' } }],
2961
};
3062

63+
// @ts-expect-error Missing identity function
3164
const graph = deserializeGraph(serialized);
32-
expectTypeOf(graph).toEqualTypeOf<Graph<{ title: string }, { type: string }>>();
3365
});
3466
});

src/utils/deserializeGraph.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
11
import { Graph } from '../Graph.js';
22
import { SerializedInput } from '../types.js';
33

4-
export function deserializeGraph<Node, LinkProps>(
5-
data: SerializedInput<Node, LinkProps>,
4+
export function deserializeGraph<Node, LinkProps, NodeIdentity>(
5+
...args: Node extends object
6+
? [data: SerializedInput<Node, LinkProps>, identityFn: (node: Node) => NodeIdentity]
7+
: [data: SerializedInput<Node, LinkProps>]
68
): Graph<Node, LinkProps> {
9+
const [data, identityFn] = args;
10+
711
const g = new Graph<Node, LinkProps>();
812

13+
const nodeIdentityMap = new Map<NodeIdentity, Node>();
14+
915
data.nodes.forEach((node) => {
1016
g.addNode(node);
17+
18+
if (identityFn) {
19+
nodeIdentityMap.set(identityFn(node), node);
20+
}
1121
});
1222

1323
data.links.forEach((link) => {
14-
g.addEdge.apply(g, [link.source, link.target, link.weight, link.props] as never);
24+
if (!identityFn) {
25+
g.addEdge.apply(g, [link.source, link.target, link.weight, link.props] as never);
26+
return;
27+
}
28+
29+
const source = nodeIdentityMap.get(identityFn(link.source)) ?? link.source;
30+
const target = nodeIdentityMap.get(identityFn(link.source)) ?? link.target;
31+
32+
g.addEdge.apply(g, [source, target, link.weight, link.props] as never);
1533
});
1634

1735
return g;

0 commit comments

Comments
 (0)