Skip to content
This repository was archived by the owner on Mar 5, 2023. It is now read-only.

Commit fddc5f2

Browse files
committed
Change stable sorting to sort by keys only
1 parent 1e8bf3d commit fddc5f2

File tree

3 files changed

+117
-116
lines changed

3 files changed

+117
-116
lines changed

src/array-stable.ts

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,30 @@ import { serializeBuffer } from './buffer';
33
import { stableSerializeObject } from './object-stable';
44
import { seenObjects } from './seen-objects';
55

6+
type CompareFunction = (a: string, b: string) => number;
7+
68
/**
79
* Performs the same as stringifyArray() with the single exception that recursive calls to stringifyObject() and
810
* stringifyArray() are replaced with calls to stableStringifyObject() and stableStringifyArray() respectively.
911
*/
1012

11-
export function stableSerializeArray(
12-
arr: any[],
13-
compareFn: (a: [string, any], b: [string, any]) => number,
14-
safe: boolean,
15-
): string {
13+
export function stableSerializeArray(arr: any[], compareFn: CompareFunction, safe: boolean): string {
1614
let str = '[';
1715
let prefix = '';
1816
let i;
1917
let value;
2018

2119
for (i = 0; i < arr.length; i++) {
2220
value = arr[i];
21+
str += prefix;
2322

24-
if (typeof value === 'string') {
25-
str += prefix + serializeString(value);
26-
prefix = ',';
27-
}
28-
29-
else if (typeof value === 'number') {
30-
str += prefix + (value === Infinity || Number.isNaN(value) ? 'null' : value);
31-
prefix = ',';
32-
}
33-
34-
else if (typeof value === 'boolean') {
35-
str += prefix + value;
36-
prefix = ',';
37-
}
38-
39-
40-
else if (typeof value === 'object' && value !== null) {
41-
if (Buffer.isBuffer(value)) {
42-
str += prefix + serializeBuffer(value);
23+
if (typeof value === 'object' && value !== null) {
24+
if (value instanceof Date) {
25+
str += '"' + value.toISOString() + '"';
4326
}
4427

45-
else if (value instanceof Date) {
46-
str += prefix + '"' + value.toISOString() + '"';
28+
else if (Buffer.isBuffer(value)) {
29+
str += serializeBuffer(value);
4730
}
4831

4932
else if (safe) {
@@ -54,7 +37,6 @@ export function stableSerializeArray(
5437
try {
5538
seenObjects.add(value);
5639

57-
str += prefix;
5840
str += Array.isArray(value)
5941
? stableSerializeArray(value, compareFn, true)
6042
: stableSerializeObject(value, compareFn, true);
@@ -66,23 +48,35 @@ export function stableSerializeArray(
6648
}
6749

6850
else {
69-
str += prefix;
7051
str += Array.isArray(value)
7152
? stableSerializeArray(value, compareFn, false)
7253
: stableSerializeObject(value, compareFn, false);
7354
}
55+
}
7456

75-
prefix = ',';
57+
else if (typeof value === 'string') {
58+
str += serializeString(value);
59+
}
60+
61+
else if (typeof value === 'number') {
62+
str += value === Infinity || Number.isNaN(value)
63+
? 'null'
64+
: value;
65+
}
66+
67+
else if (typeof value === 'boolean') {
68+
str += value;
7669
}
7770

7871
else if (typeof value === 'bigint') {
7972
throw TypeError('Cannot serialize objects that contain a BigInt');
8073
}
8174

8275
else {
83-
str += prefix + 'null';
84-
prefix = ',';
76+
str += 'null';
8577
}
78+
79+
prefix = ',';
8680
}
8781

8882
return str + ']';

src/index.ts

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,47 @@ import { serializeObject } from './object';
55
import { stableSerializeArray } from './array-stable';
66
import { stableSerializeObject } from './object-stable';
77

8-
type CompareFunction = (a: [string, any], b: [string, any]) => number;
8+
type CompareFunction = (a: string, b: string) => number;
99

1010
/**
11-
* jsonStringify() converts the provided object to a JSON string and returns it. If true is passed as the second
12-
* argument then nested objects will be checked for circular references and an error will be thrown if one is found.
13-
* If false is passed then there will be no checks for circular references, which grants a considerable speed boost.
11+
* Converts the provided object to a JSON string and returns it. If true is passed as the second argument then nested
12+
* objects will be checked for circular references and an error will be thrown if one is found. If false is passed then
13+
* there will be no checks for circular references.
1414
*/
1515

1616
export function jsonStringify(value: any, safe = true): string | undefined {
17-
if (typeof value === 'boolean') {
18-
return value.toString();
19-
}
17+
if (typeof value === 'object') {
18+
if (value === null) {
19+
return 'null';
20+
}
2021

21-
else if (typeof value === 'number') {
22-
return (value === Infinity || Number.isNaN(value) ? 'null' : value.toString());
22+
if (value instanceof Date) {
23+
return '"' + value.toISOString() + '"';
24+
}
25+
26+
if (Buffer.isBuffer(value)) {
27+
return serializeBuffer(value);
28+
}
29+
30+
if (Array.isArray(value)) {
31+
return serializeArray(value, safe);
32+
}
33+
34+
return serializeObject(value, safe);
2335
}
2436

2537
else if (typeof value === 'string') {
2638
return serializeString(value);
2739
}
2840

29-
else if (value === null) {
30-
return 'null';
41+
else if (typeof value === 'number') {
42+
return (value === Infinity || Number.isNaN(value))
43+
? 'null'
44+
: value.toString();
3145
}
3246

33-
else if (typeof value === 'object') {
34-
if (Buffer.isBuffer(value)) {
35-
return serializeBuffer(value);
36-
}
37-
38-
else if (value instanceof Date) {
39-
return '"' + value.toISOString() + '"';
40-
}
41-
42-
else {
43-
return Array.isArray(value)
44-
? serializeArray(value, safe)
45-
: serializeObject(value, safe);
46-
}
47+
else if (typeof value === 'boolean') {
48+
return value.toString();
4749
}
4850

4951
else if (typeof value === 'bigint') {
@@ -54,44 +56,46 @@ export function jsonStringify(value: any, safe = true): string | undefined {
5456
}
5557

5658
/**
57-
* stableJsonStringify() is a deterministic version of jsonStringify(). It works the same with the exception that object
58-
* properties are sorted before being serialized, resulting in consistent output for the same input. By default object
59-
* keys are sorted alphabetically, but a custom compare function can be passed to control the sorting behavior.
59+
* A deterministic version of jsonStringify(). It works the same with the exception that object properties are sorted
60+
* before being serialized, resulting in consistent output for the same input. By default object keys are sorted
61+
* alphabetically, but a custom compare function can be passed to control the sorting behavior.
6062
*/
6163

6264
export function stableJsonStringify(value: any, compareFn?: CompareFunction | null, safe = true): string | undefined {
63-
if (typeof value === 'boolean') {
64-
return value.toString();
65-
}
66-
67-
else if (typeof value === 'number') {
68-
return (value === Infinity || Number.isNaN(value) ? 'null' : value.toString());
69-
}
70-
71-
else if (typeof value === 'string') {
72-
return serializeString(value);
73-
}
65+
if (typeof value === 'object') {
66+
if (value === null) {
67+
return 'null';
68+
}
7469

75-
else if (value === null) {
76-
return 'null';
77-
}
70+
if (value instanceof Date) {
71+
return '"' + value.toISOString() + '"';
72+
}
7873

79-
else if (typeof value === 'object') {
8074
if (Buffer.isBuffer(value)) {
8175
return serializeBuffer(value);
8276
}
8377

84-
else if (value instanceof Date) {
85-
return '"' + value.toISOString() + '"';
78+
compareFn = compareFn ?? defaultCompareKeys;
79+
80+
if (Array.isArray(value)) {
81+
return stableSerializeArray(value, compareFn, safe);
8682
}
8783

88-
else {
89-
compareFn = typeof compareFn === 'function' ? compareFn : defaultCompareKeys;
84+
return stableSerializeObject(value, compareFn, safe);
85+
}
9086

91-
return Array.isArray(value)
92-
? stableSerializeArray(value, compareFn, safe)
93-
: stableSerializeObject(value, compareFn, safe);
94-
}
87+
else if (typeof value === 'string') {
88+
return serializeString(value);
89+
}
90+
91+
else if (typeof value === 'number') {
92+
return (value === Infinity || Number.isNaN(value))
93+
? 'null'
94+
: value.toString();
95+
}
96+
97+
else if (typeof value === 'boolean') {
98+
return value.toString();
9599
}
96100

97101
else if (typeof value === 'bigint') {
@@ -101,6 +105,6 @@ export function stableJsonStringify(value: any, compareFn?: CompareFunction | nu
101105
return;
102106
}
103107

104-
function defaultCompareKeys([keyA]: string[], [keyB]: string[]) {
105-
return keyA > keyB ? 1 : -1;
108+
function defaultCompareKeys(a: string, b: string) {
109+
return a > b ? 1 : -1;
106110
}

src/object-stable.ts

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,39 @@ import { serializeBuffer } from './buffer';
33
import { stableSerializeArray } from './array-stable';
44
import { seenObjects } from './seen-objects';
55

6+
type CompareFunction = (a: string, b: string) => number;
7+
68
/**
79
* Performs the same function as stringifyObject(), except that object entries are sorted using the
810
* provided comparison function before being serialized.
911
*/
1012

11-
export function stableSerializeObject(
12-
obj: any,
13-
compareFn: (a: [string, any], b: [string, any]) => number,
14-
safe: boolean,
15-
): string {
16-
const entries = Object.entries(obj).sort(compareFn);
13+
export function stableSerializeObject(obj: { [key: string]: any }, compareFn: CompareFunction, safe: boolean): string {
14+
const keys = Object.keys(obj).sort(compareFn);
1715

1816
let str = '{';
1917
let prefix = '"';
2018
let i;
2119
let key;
2220
let value;
2321

24-
for (i = 0; i < entries.length; i++) {
25-
[key, value] = entries[i];
22+
for (i = 0; i < keys.length; i++) {
23+
key = keys[i];
24+
value = obj[key];
2625

27-
if (typeof value === 'string') {
28-
str += prefix + key + '":' + serializeString(value);
29-
prefix = ',"';
30-
}
26+
if (typeof value === 'object') {
27+
str += prefix + key;
3128

32-
else if (typeof value === 'boolean') {
33-
str += prefix + key + '":' + value;
34-
prefix = ',"';
35-
}
36-
37-
else if (typeof value === 'number') {
38-
str += prefix + key + (value === Infinity || Number.isNaN(value) ? '":null' : '":' + value);
39-
prefix = ',"';
40-
}
41-
42-
else if (typeof value === 'object') {
4329
if (value === null) {
44-
str += prefix + key + '":null';
45-
prefix = ',"';
30+
str += '":null';
4631
}
4732

48-
else if (Buffer.isBuffer(value)) {
49-
str += prefix + key + '":' + serializeBuffer(value);
33+
else if (value instanceof Date) {
34+
str += '":"' + value.toISOString() + '"';
5035
}
5136

52-
else if (value instanceof Date) {
53-
str += prefix + key + '":"' + value.toISOString() + '"';
37+
else if (Buffer.isBuffer(value)) {
38+
str += '":' + serializeBuffer(value);
5439
}
5540

5641
else if (safe) {
@@ -61,7 +46,7 @@ export function stableSerializeObject(
6146
try {
6247
seenObjects.add(value);
6348

64-
str += prefix + key + '":';
49+
str += '":';
6550
str += Array.isArray(value)
6651
? stableSerializeArray(value, compareFn, true)
6752
: stableSerializeObject(value, compareFn, true);
@@ -73,15 +58,33 @@ export function stableSerializeObject(
7358
}
7459

7560
else {
76-
str += prefix + key + '":';
61+
str += '":';
7762
str += Array.isArray(value)
78-
? stableSerializeArray(value, compareFn, false)
63+
? stableSerializeArray(value, compareFn, true)
7964
: stableSerializeObject(value, compareFn, false);
8065
}
8166

8267
prefix = ',"';
8368
}
8469

70+
else if (typeof value === 'string') {
71+
str += prefix + key + '":' + serializeString(value);
72+
prefix = ',"';
73+
}
74+
75+
else if (typeof value === 'number') {
76+
str += prefix + key;
77+
str += value === Infinity || Number.isNaN(value)
78+
? '":null'
79+
: ('":' + value);
80+
prefix = ',"';
81+
}
82+
83+
else if (typeof value === 'boolean') {
84+
str += prefix + key + '":' + value;
85+
prefix = ',"';
86+
}
87+
8588
else if (typeof value === 'bigint') {
8689
throw TypeError('Cannot serialize objects that contain a BigInt');
8790
}

0 commit comments

Comments
 (0)