Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
cool
Browse files Browse the repository at this point in the history
  • Loading branch information
KATT committed Oct 1, 2023
1 parent e89e1fc commit f36924f
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 3 deletions.
37 changes: 37 additions & 0 deletions src/tson.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect, test } from "vitest";

import { expectError } from "./testUtils.js";
import { createTson } from "./tson.js";
import { TsonType } from "./types.js";

Expand Down Expand Up @@ -31,3 +32,39 @@ test("duplicate keys", () => {
'"Multiple handlers for key string found"',
);
});

test("no max call stack", () => {
const t = createTson({
types: [],
});

const expected: Record<string, unknown> = {};
expected["a"] = expected;

// stringify should fail b/c of JSON limitations
const err = expectError(() => t.stringify(expected));

expect(err.message).toMatchInlineSnapshot('"Circular reference detected"');
});

test("allow duplicate objects", () => {
const t = createTson({
types: [],
});

const obj = {
a: 1,
b: 2,
c: 3,
};

const expected = {
a: obj,
b: obj,
c: obj,
};

const actual = t.deserialize(t.serialize(expected));

expect(actual).toEqual(expected);
});
44 changes: 41 additions & 3 deletions src/tson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ export function createTsonStringify(opts: TsonOptions): TsonStringifyFn {
JSON.stringify(serializer(obj), null, space)) as TsonStringifyFn;
}

export class CircularReferenceError extends Error {
/**
* The circular reference that was found
*/
public readonly value;

constructor(value: unknown) {
super(`Circular reference detected`);
this.name = this.constructor.name;
this.value = value;
}
}

export function createTsonSerialize(opts: TsonOptions): TsonSerializeFn {
const handlers = (() => {
const types = opts.types.map((handler) => {
Expand Down Expand Up @@ -124,24 +137,49 @@ export function createTsonSerialize(opts: TsonOptions): TsonSerializeFn {
const [nonPrimitive, byPrimitive] = handlers;

const walker: WalkerFactory = (nonce) => {
const seen = new WeakSet();
const cache = new WeakMap<object, unknown>();

const walk: WalkFn = (value) => {
const type = typeof value;
const isComplex = !!value && type === "object";

if (isComplex) {
if (seen.has(value)) {
const cached = cache.get(value);
if (!cached) {
throw new CircularReferenceError(value);
}

return cached;
}

seen.add(value);
}

const cacheAndReturn = (result: unknown) => {
if (isComplex) {
cache.set(value, result);
}

return result;
};

const primitiveHandler = byPrimitive[type];
if (
primitiveHandler &&
(!primitiveHandler.test || primitiveHandler.test(value))
) {
return primitiveHandler.$serialize(value, nonce, walk);
return cacheAndReturn(primitiveHandler.$serialize(value, nonce, walk));
}

for (const handler of nonPrimitive) {
if (handler.test(value)) {
return handler.$serialize(value, nonce, walk);
return cacheAndReturn(handler.$serialize(value, nonce, walk));
}
}

return mapOrReturn(value, walk);
return cacheAndReturn(mapOrReturn(value, walk));
};

return walk;
Expand Down

0 comments on commit f36924f

Please sign in to comment.