-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsort.ts
More file actions
135 lines (129 loc) · 4.15 KB
/
sort.ts
File metadata and controls
135 lines (129 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* @fileoverview Sorted-object helpers: `entryKeyComparator`,
* `objectEntries`, `toSortedObject`, `toSortedObjectFromEntries`.
*
* Symbol keys sort separately from string keys (placed first in the
* resulting object) — this matters for serialization stability since
* `Object.keys` and `JSON.stringify` will iterate insertion order.
*/
import { ObjectFromEntries } from '../primordials/object'
import { ReflectOwnKeys } from '../primordials/reflect'
import { localeCompare } from '../sorts/natural'
import type { SortedObject } from './types'
/**
* Compare two entry arrays by their keys for sorting.
*
* Used internally for alphabetically sorting object entries.
* String keys are compared directly, non-string keys are converted to strings first.
*
* @param a - First entry tuple [key, value]
* @param b - Second entry tuple [key, value]
* @returns Negative if a < b, positive if a > b, zero if equal
*
* @example
* ```ts
* const entries = [['zebra', 1], ['apple', 2], ['banana', 3]]
* entries.sort(entryKeyComparator)
* // [['apple', 2], ['banana', 3], ['zebra', 1]]
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function entryKeyComparator(
a: [PropertyKey, unknown],
b: [PropertyKey, unknown],
): number {
const keyA = a[0]
const keyB = b[0]
const strKeyA = typeof keyA === 'string' ? keyA : String(keyA)
const strKeyB = typeof keyB === 'string' ? keyB : String(keyB)
return localeCompare(strKeyA, strKeyB)
}
/**
* Get all own property entries (key-value pairs) from an object.
*
* Unlike `Object.entries()`, this includes non-enumerable properties and
* symbol keys. Returns an empty array for null/undefined.
*
* @param obj - The object to get entries from
* @returns Array of [key, value] tuples, or empty array for null/undefined
*
* @example
* ```ts
* objectEntries({ a: 1, b: 2 }) // [['a', 1], ['b', 2]]
* objectEntries({ [Symbol('k')]: 'v', x: 10 }) // [[Symbol(k), 'v'], ['x', 10]]
* objectEntries(null) // []
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function objectEntries(obj: unknown): Array<[PropertyKey, unknown]> {
if (obj === null || obj === undefined) {
return []
}
const keys = ReflectOwnKeys(obj as object)
const { length } = keys
const entries = Array(length)
const record = obj as Record<PropertyKey, unknown>
for (let i = 0; i < length; i += 1) {
const key = keys[i] as PropertyKey
entries[i] = [key, record[key]]
}
return entries
}
/**
* Convert an object to a new object with sorted keys.
*
* Creates a new object with the same properties as the input, but with keys
* sorted alphabetically. Symbol keys are sorted separately and placed first.
* This is useful for consistent key ordering in serialization or comparisons.
*
* @param obj - The object to sort
* @returns A new object with sorted keys
*
* @example
* ```ts
* toSortedObject({ z: 1, a: 2, m: 3 }) // { a: 2, m: 3, z: 1 }
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function toSortedObject<T extends object>(obj: T): T {
return toSortedObjectFromEntries(objectEntries(obj)) as T
}
/**
* Create an object from entries with sorted keys.
*
* Takes an iterable of [key, value] entries and creates a new object with
* keys sorted alphabetically. Symbol keys are sorted separately and placed
* first in the resulting object.
*
* @param entries - Iterable of [key, value] tuples
* @returns A new object with sorted keys
*
* @example
* ```ts
* toSortedObjectFromEntries([['z', 1], ['a', 2], ['m', 3]])
* // { a: 2, m: 3, z: 1 }
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function toSortedObjectFromEntries<T = unknown>(
entries: Iterable<[PropertyKey, T]>,
): SortedObject<T> {
const otherEntries = []
const symbolEntries = []
// Use for-of to work with entries iterators.
for (const entry of entries) {
if (typeof entry[0] === 'symbol') {
symbolEntries.push(entry)
} else {
otherEntries.push(entry)
}
}
if (!otherEntries.length && !symbolEntries.length) {
return {}
}
return ObjectFromEntries([
// The String constructor is safe to use with symbols.
...symbolEntries.sort(entryKeyComparator),
...otherEntries.sort(entryKeyComparator),
])
}