diff --git a/src/index.ts b/src/index.ts index fec956f..88d04d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,7 @@ const escaped: Record<string, string> = { '<': '\\u003C', '>' : '\\u003E', '/': const objectProtoOwnPropertyNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0'); export default function devalue(value: any) { - const repeated = new Map(); - const seen = new Set(); + const counts = new Map(); let n = 0; @@ -15,12 +14,12 @@ export default function devalue(value: any) { throw new Error(`Cannot stringify a function`); } - if (seen.has(thing)) { - repeated.set(thing, getName(n++)); + if (counts.has(thing)) { + counts.set(thing, counts.get(thing) + 1); return; } - seen.add(thing); + counts.set(thing, 1); if (!isPrimitive(thing)) { const type = getType(thing); @@ -46,11 +45,11 @@ export default function devalue(value: any) { const proto = Object.getPrototypeOf(thing); if ( - proto !== Object.prototype && - proto !== null && - Object.getOwnPropertyNames(proto).sort().join('\0') !== objectProtoOwnPropertyNames + proto !== Object.prototype && + proto !== null && + Object.getOwnPropertyNames(proto).sort().join('\0') !== objectProtoOwnPropertyNames ) { - throw new Error(`Cannot stringify arbitrary non-POJOs`); + throw new Error(`Cannot stringify arbitrary non-POJOs`); } if (Object.getOwnPropertySymbols(thing).length > 0) { @@ -62,9 +61,20 @@ export default function devalue(value: any) { } } + walk(value); + + const names = new Map(); + + Array.from(counts) + .filter(entry => entry[1] > 1) + .sort((a, b) => b[1] - a[1]) + .forEach((entry, i) => { + names.set(entry[0], getName(i)); + }); + function stringify(thing: any): string { - if (repeated.has(thing)) { - return repeated.get(thing); + if (names.has(thing)) { + return names.get(thing); } if (isPrimitive(thing)) { @@ -107,15 +117,14 @@ export default function devalue(value: any) { } } - walk(value); const str = stringify(value); - if (repeated.size) { + if (names.size) { const params: string[] = []; const statements: string[] = []; const values: string[] = []; - repeated.forEach((name, thing) => { + names.forEach((name, thing) => { params.push(name); if (isPrimitive(thing)) { @@ -196,7 +205,9 @@ function stringifyPrimitive(thing: any) { if (typeof thing === 'string') return JSON.stringify(thing).replace(unsafe, escape); if (thing === void 0) return 'void 0'; if (thing === 0 && 1 / thing < 0) return '-0'; - return String(thing); + const str = String(thing); + if (typeof thing === 'number') return str.replace(/^(-)?0\./, '$1.'); + return str; } function getType(thing: any) { diff --git a/test/test.ts b/test/test.ts index 4865963..74e69bd 100644 --- a/test/test.ts +++ b/test/test.ts @@ -14,6 +14,8 @@ describe('devalue', () => { test('number', 42, '42'); test('negative number', -42, '-42'); test('negative zero', -0, '-0'); + test('positive decimal', 0.1, '.1'); + test('negative decimal', -0.1, '-.1'); test('string', 'woo!!!', '"woo!!!"'); test('boolean', true, 'true'); test('Number', new Number(42), 'Object(42)');