1
|
|
|
// Type definitions for object types and non-sortable types |
2
|
|
|
type ObjectType = Record<string | symbol, unknown>; |
3
|
|
|
type SortedEntry = [string | symbol, unknown]; |
4
|
|
|
type NonSortableType = |
5
|
|
|
| Date |
6
|
|
|
| RegExp |
7
|
|
|
| (() => unknown) |
8
|
|
|
| ((...args: unknown[]) => unknown) |
9
|
|
|
| Error |
10
|
|
|
| Map<unknown, unknown> |
11
|
|
|
| Set<unknown> |
12
|
|
|
| WeakMap<object, unknown> |
13
|
|
|
| WeakSet<object> |
14
|
|
|
| Promise<unknown>; |
15
|
|
|
|
16
|
|
|
// Main sorting function that can handle various data types |
17
|
1 |
|
export function sort<T>( |
18
|
|
|
data: T, |
19
|
|
|
ascending = true, |
20
|
|
|
sortPrimitiveArrays = false |
21
|
|
|
): T { |
22
|
40 |
|
return sortRecursively(data, ascending, sortPrimitiveArrays); |
23
|
|
|
} |
24
|
|
|
|
25
|
|
|
// Recursive function to sort nested structures |
26
|
|
|
function sortRecursively<T>( |
27
|
|
|
data: T, |
28
|
|
|
ascending: boolean, |
29
|
|
|
sortPrimitiveArrays: boolean |
30
|
|
|
): T { |
31
|
201 |
|
if (!isObject(data) || Array.isArray(data) || isNonSortableObject(data)) { |
32
|
157 |
|
if (Array.isArray(data)) { |
33
|
|
|
// If sortPrimitiveArrays is true and all items are primitives, sort the array |
34
|
39 |
|
if ( |
35
|
|
|
sortPrimitiveArrays && |
36
|
|
|
data.length > 0 && |
37
|
65 |
|
data.every((item) => isPrimitive(item)) |
38
|
|
|
) { |
39
|
16 |
|
return [...data].sort((a, b) => { |
40
|
60 |
|
if (typeof a === 'string' && typeof b === 'string') { |
41
|
9 |
|
return ascending ? a.localeCompare(b) : b.localeCompare(a); |
42
|
|
|
} |
43
|
51 |
|
if (typeof a === 'number' && typeof b === 'number') { |
44
|
36 |
|
return ascending ? a - b : b - a; |
45
|
|
|
} |
46
|
15 |
|
if (typeof a === 'boolean' && typeof b === 'boolean') { |
47
|
10 |
|
return ascending |
48
|
|
|
? a === b |
49
|
|
|
? 0 |
50
|
|
|
: a |
51
|
|
|
? 1 |
52
|
|
|
: -1 |
53
|
|
|
: a === b |
54
|
|
|
? 0 |
55
|
|
|
: a |
56
|
|
|
? -1 |
57
|
|
|
: 1; |
58
|
|
|
} |
59
|
|
|
// For mixed types or other primitives, maintain original order |
60
|
5 |
|
return 0; |
61
|
|
|
}) as T; |
62
|
|
|
} |
63
|
|
|
// Otherwise, recursively sort array items |
64
|
23 |
|
return data.map((item) => |
65
|
58 |
|
sortRecursively(item, ascending, sortPrimitiveArrays) |
66
|
|
|
) as T; |
67
|
|
|
} |
68
|
118 |
|
return data; |
69
|
|
|
} |
70
|
|
|
|
71
|
44 |
|
return sortObject(data as ObjectType, ascending, sortPrimitiveArrays) as T; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// Helper function to check if a value is an object |
75
|
|
|
function isObject(data: unknown): data is ObjectType { |
76
|
201 |
|
return typeof data === 'object' && data !== null; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
function isPrimitive(data: unknown): boolean { |
80
|
65 |
|
return ( |
81
|
|
|
typeof data === 'string' || |
82
|
|
|
typeof data === 'number' || |
83
|
|
|
typeof data === 'boolean' |
84
|
|
|
); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// Function to sort object properties |
88
|
|
|
function sortObject( |
89
|
|
|
obj: ObjectType, |
90
|
|
|
ascending: boolean, |
91
|
|
|
sortPrimitiveArrays: boolean |
92
|
|
|
): ObjectType { |
93
|
44 |
|
const entries = [ |
94
|
|
|
...Object.entries(obj), |
95
|
|
|
...Object.getOwnPropertySymbols(obj).map( |
96
|
2 |
|
(sym) => [sym, obj[sym]] as SortedEntry |
97
|
|
|
), |
98
|
|
|
]; |
99
|
|
|
|
100
|
44 |
|
const sortedEntries = entries.sort(([keyA], [keyB]) => { |
101
|
81 |
|
if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0; |
102
|
80 |
|
if (typeof keyA === 'symbol') return 1; |
103
|
77 |
|
if (typeof keyB === 'symbol') return -1; |
104
|
77 |
|
return ascending |
105
|
|
|
? (keyA as string).localeCompare(keyB as string) |
106
|
|
|
: (keyB as string).localeCompare(keyA as string); |
107
|
|
|
}); |
108
|
|
|
|
109
|
44 |
|
return Object.fromEntries( |
110
|
103 |
|
sortedEntries.map(([key, value]) => [ |
111
|
|
|
key, |
112
|
|
|
sortRecursively(value, ascending, sortPrimitiveArrays), |
113
|
|
|
]) |
114
|
|
|
); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
// Helper function to check if an object is of a non-sortable type |
118
|
|
|
function isNonSortableObject(obj: unknown): obj is NonSortableType { |
119
|
61 |
|
const nonSortableTypes = [ |
120
|
|
|
Date, |
121
|
|
|
RegExp, |
122
|
|
|
Function, |
123
|
|
|
Error, |
124
|
|
|
Map, |
125
|
|
|
Set, |
126
|
|
|
WeakMap, |
127
|
|
|
WeakSet, |
128
|
|
|
Promise, |
129
|
|
|
]; |
130
|
61 |
|
return ( |
131
|
459 |
|
nonSortableTypes.some((type) => obj instanceof type) || |
132
|
|
|
Symbol.iterator in Object(obj) |
133
|
|
|
); |
134
|
|
|
} |
135
|
|
|
|