@tamtamchik/json-deep-sort 1.4.0-beta.3 → 1.4.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +115 -178
- package/dist/index.mjs +115 -178
- package/package.json +1 -1
- package/src/array-sorter.ts +61 -0
- package/src/comparators.ts +59 -0
- package/src/core.ts +20 -0
- package/src/index.ts +9 -95
- package/src/object-sorter.ts +34 -65
- package/src/type-guards.ts +43 -66
- package/src/types.ts +29 -0
- package/src/primitive-array-sorter.ts +0 -151
package/dist/index.js
CHANGED
|
@@ -24,188 +24,113 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
|
-
// src/
|
|
28
|
-
var SORTABLE_PRIMITIVE_TYPES = ["string", "number", "boolean"];
|
|
27
|
+
// src/types.ts
|
|
29
28
|
var NULLISH_VALUES = [null, void 0];
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!sortPrimitiveArrays || !this.canSortPrimitiveArray(array)) {
|
|
36
|
-
return array;
|
|
37
|
-
}
|
|
38
|
-
return [...array].sort(
|
|
39
|
-
(a, b) => this.compareSortablePrimitives(a, b, ascending)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Checks if an array can be sorted as primitives
|
|
44
|
-
*/
|
|
45
|
-
static canSortPrimitiveArray(array) {
|
|
46
|
-
return this.allItemsAreSortablePrimitives(array) && this.allItemsHaveSameSortableType(array);
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Checks if all items are sortable primitives
|
|
50
|
-
*/
|
|
51
|
-
static allItemsAreSortablePrimitives(array) {
|
|
52
|
-
return array.every((item) => this.isSortablePrimitive(item));
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Checks if all items have the same sortable type
|
|
56
|
-
*/
|
|
57
|
-
static allItemsHaveSameSortableType(array) {
|
|
58
|
-
if (array.length === 0) return true;
|
|
59
|
-
if (this.hasNullishValues(array)) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
const firstItem = array[0];
|
|
63
|
-
if (!this.isSortablePrimitive(firstItem)) return false;
|
|
64
|
-
const firstType = typeof firstItem;
|
|
65
|
-
return array.every((item) => typeof item === firstType);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Compares two sortable primitive values
|
|
69
|
-
*/
|
|
70
|
-
static compareSortablePrimitives(a, b, ascending) {
|
|
71
|
-
if (typeof a === "string" && typeof b === "string") {
|
|
72
|
-
return this.compareStrings(a, b, ascending);
|
|
73
|
-
}
|
|
74
|
-
if (typeof a === "number" && typeof b === "number") {
|
|
75
|
-
return this.compareNumbers(a, b, ascending);
|
|
76
|
-
}
|
|
77
|
-
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
78
|
-
return this.compareBooleans(a, b, ascending);
|
|
79
|
-
}
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Compares two strings
|
|
84
|
-
*/
|
|
85
|
-
static compareStrings(a, b, ascending) {
|
|
86
|
-
const result = a.localeCompare(b);
|
|
87
|
-
return ascending ? result : -result;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Compares two numbers
|
|
91
|
-
*/
|
|
92
|
-
static compareNumbers(a, b, ascending) {
|
|
93
|
-
if (isNaN(a) && isNaN(b)) return 0;
|
|
94
|
-
if (isNaN(a)) return 1;
|
|
95
|
-
if (isNaN(b)) return -1;
|
|
96
|
-
const result = a - b;
|
|
97
|
-
return ascending ? result : -result;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Compares two booleans
|
|
101
|
-
*/
|
|
102
|
-
static compareBooleans(a, b, ascending) {
|
|
103
|
-
if (a === b) return 0;
|
|
104
|
-
if (a) return ascending ? 1 : -1;
|
|
105
|
-
return ascending ? -1 : 1;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Checks if a value is a sortable primitive
|
|
109
|
-
*/
|
|
110
|
-
static isSortablePrimitive(value) {
|
|
111
|
-
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
112
|
-
typeof value
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Checks if an array has nullish values
|
|
117
|
-
*/
|
|
118
|
-
static hasNullishValues(array) {
|
|
119
|
-
return array.some(this.isNullish);
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Checks if a value is nullish
|
|
123
|
-
*/
|
|
124
|
-
static isNullish(value) {
|
|
125
|
-
return NULLISH_VALUES.includes(value);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
29
|
+
var SORTABLE_PRIMITIVE_TYPES = [
|
|
30
|
+
"string",
|
|
31
|
+
"number",
|
|
32
|
+
"boolean"
|
|
33
|
+
];
|
|
128
34
|
|
|
129
35
|
// src/type-guards.ts
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* Checks if a value is a primitive (string, number, boolean, null, undefined)
|
|
133
|
-
*/
|
|
134
|
-
static isPrimitive(data) {
|
|
135
|
-
return this.isSortablePrimitive(data) || this.isNullish(data);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Checks if a value is a sortable primitive (string, number, boolean)
|
|
139
|
-
*/
|
|
140
|
-
static isSortablePrimitive(value) {
|
|
141
|
-
const sortableTypes = ["string", "number", "boolean"];
|
|
142
|
-
return sortableTypes.includes(typeof value);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Checks if a value is nullish (null or undefined)
|
|
146
|
-
*/
|
|
147
|
-
static isNullish(value) {
|
|
148
|
-
return value === null || value === void 0;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Checks if a value is a plain object
|
|
152
|
-
*/
|
|
153
|
-
static isObject(data) {
|
|
154
|
-
return typeof data === "object" && data !== null;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Checks if an object is non-sortable (Date, RegExp, Function, etc.)
|
|
158
|
-
*/
|
|
159
|
-
static isNonSortableObject(obj) {
|
|
160
|
-
const nonSortableTypes = [
|
|
161
|
-
Date,
|
|
162
|
-
RegExp,
|
|
163
|
-
Function,
|
|
164
|
-
Error,
|
|
165
|
-
Map,
|
|
166
|
-
Set,
|
|
167
|
-
WeakMap,
|
|
168
|
-
WeakSet,
|
|
169
|
-
Promise
|
|
170
|
-
];
|
|
171
|
-
return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Checks if an array has nullish values
|
|
175
|
-
*/
|
|
176
|
-
static hasNullishValues(array) {
|
|
177
|
-
return array.some(this.isNullish);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// src/index.ts
|
|
182
|
-
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
183
|
-
const options = { ascending, sortPrimitiveArrays };
|
|
184
|
-
return sortRecursively(data, options);
|
|
36
|
+
function isNullish(value) {
|
|
37
|
+
return NULLISH_VALUES.includes(value);
|
|
185
38
|
}
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
39
|
+
function hasNullishValues(array) {
|
|
40
|
+
return array.some(isNullish);
|
|
41
|
+
}
|
|
42
|
+
function isSortablePrimitive(value) {
|
|
43
|
+
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
44
|
+
typeof value
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function isPrimitive(data) {
|
|
48
|
+
return isSortablePrimitive(data) || isNullish(data);
|
|
49
|
+
}
|
|
50
|
+
function isObject(data) {
|
|
51
|
+
return typeof data === "object" && data !== null;
|
|
52
|
+
}
|
|
53
|
+
function isNonSortableObject(obj) {
|
|
54
|
+
const nonSortableTypes = [
|
|
55
|
+
Date,
|
|
56
|
+
RegExp,
|
|
57
|
+
Function,
|
|
58
|
+
Error,
|
|
59
|
+
Map,
|
|
60
|
+
Set,
|
|
61
|
+
WeakMap,
|
|
62
|
+
WeakSet,
|
|
63
|
+
Promise
|
|
64
|
+
];
|
|
65
|
+
return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/comparators.ts
|
|
69
|
+
function compareSortablePrimitives(a, b, ascending) {
|
|
70
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
71
|
+
return ascending ? a.localeCompare(b) : b.localeCompare(a);
|
|
189
72
|
}
|
|
190
|
-
if (
|
|
191
|
-
return
|
|
73
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
74
|
+
return compareNumbers(a, b, ascending);
|
|
192
75
|
}
|
|
193
|
-
if (
|
|
194
|
-
return
|
|
76
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
77
|
+
return compareBooleans(a, b, ascending);
|
|
195
78
|
}
|
|
196
|
-
return
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
function compareNumbers(a, b, ascending) {
|
|
82
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return 0;
|
|
83
|
+
if (Number.isNaN(a)) return 1;
|
|
84
|
+
if (Number.isNaN(b)) return -1;
|
|
85
|
+
return ascending ? a - b : b - a;
|
|
86
|
+
}
|
|
87
|
+
function compareBooleans(a, b, ascending) {
|
|
88
|
+
if (a === b) return 0;
|
|
89
|
+
if (a) return ascending ? 1 : -1;
|
|
90
|
+
return ascending ? -1 : 1;
|
|
91
|
+
}
|
|
92
|
+
function compareObjectKeys(keyA, keyB, ascending) {
|
|
93
|
+
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
94
|
+
if (typeof keyA === "symbol") return 1;
|
|
95
|
+
if (typeof keyB === "symbol") return -1;
|
|
96
|
+
const stringA = keyA;
|
|
97
|
+
const stringB = keyB;
|
|
98
|
+
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
197
99
|
}
|
|
100
|
+
|
|
101
|
+
// src/array-sorter.ts
|
|
198
102
|
function sortArray(array, options) {
|
|
199
|
-
|
|
200
|
-
array,
|
|
201
|
-
options.ascending,
|
|
202
|
-
options.sortPrimitiveArrays
|
|
203
|
-
);
|
|
204
|
-
if (sortedPrimitiveArray !== array) {
|
|
205
|
-
return sortedPrimitiveArray;
|
|
103
|
+
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
|
|
104
|
+
return sortPrimitiveArray(array, options.ascending);
|
|
206
105
|
}
|
|
207
106
|
return array.map((item) => sortRecursively(item, options));
|
|
208
107
|
}
|
|
108
|
+
function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
|
|
109
|
+
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
110
|
+
}
|
|
111
|
+
function canSortPrimitiveArray(array) {
|
|
112
|
+
return allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array);
|
|
113
|
+
}
|
|
114
|
+
function allItemsAreSortablePrimitives(array) {
|
|
115
|
+
return array.every(isSortablePrimitive);
|
|
116
|
+
}
|
|
117
|
+
function sortPrimitiveArray(array, ascending) {
|
|
118
|
+
return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
|
|
119
|
+
}
|
|
120
|
+
function allItemsHaveSameSortableType(array) {
|
|
121
|
+
if (array.length === 0) return true;
|
|
122
|
+
if (hasNullishValues(array)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const firstItem = array[0];
|
|
126
|
+
if (!isSortablePrimitive(firstItem)) return false;
|
|
127
|
+
const expectedType = typeof firstItem;
|
|
128
|
+
const allSameType = array.every((item) => typeof item === expectedType);
|
|
129
|
+
if (allSameType) return true;
|
|
130
|
+
return array.every(isSortablePrimitive);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/object-sorter.ts
|
|
209
134
|
function sortObject(obj, options) {
|
|
210
135
|
const entries = collectObjectEntries(obj);
|
|
211
136
|
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
@@ -223,14 +148,6 @@ function sortObjectEntries(entries, ascending) {
|
|
|
223
148
|
([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
|
|
224
149
|
);
|
|
225
150
|
}
|
|
226
|
-
function compareObjectKeys(keyA, keyB, ascending) {
|
|
227
|
-
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
228
|
-
if (typeof keyA === "symbol") return 1;
|
|
229
|
-
if (typeof keyB === "symbol") return -1;
|
|
230
|
-
const stringA = keyA;
|
|
231
|
-
const stringB = keyB;
|
|
232
|
-
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
233
|
-
}
|
|
234
151
|
function createSortedObject(entries, options) {
|
|
235
152
|
const sortedEntries = entries.map(([key, value]) => [
|
|
236
153
|
key,
|
|
@@ -238,6 +155,26 @@ function createSortedObject(entries, options) {
|
|
|
238
155
|
]);
|
|
239
156
|
return Object.fromEntries(sortedEntries);
|
|
240
157
|
}
|
|
158
|
+
|
|
159
|
+
// src/core.ts
|
|
160
|
+
function sortRecursively(data, options) {
|
|
161
|
+
if (Array.isArray(data)) {
|
|
162
|
+
return sortArray(data, options);
|
|
163
|
+
}
|
|
164
|
+
if (isPrimitive(data) || isNonSortableObject(data)) {
|
|
165
|
+
return data;
|
|
166
|
+
}
|
|
167
|
+
if (isObject(data)) {
|
|
168
|
+
return sortObject(data, options);
|
|
169
|
+
}
|
|
170
|
+
return data;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/index.ts
|
|
174
|
+
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
175
|
+
const options = { ascending, sortPrimitiveArrays };
|
|
176
|
+
return sortRecursively(data, options);
|
|
177
|
+
}
|
|
241
178
|
// Annotate the CommonJS export names for ESM import in node:
|
|
242
179
|
0 && (module.exports = {
|
|
243
180
|
sort
|
package/dist/index.mjs
CHANGED
|
@@ -1,185 +1,110 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
var SORTABLE_PRIMITIVE_TYPES = ["string", "number", "boolean"];
|
|
1
|
+
// src/types.ts
|
|
3
2
|
var NULLISH_VALUES = [null, void 0];
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!sortPrimitiveArrays || !this.canSortPrimitiveArray(array)) {
|
|
10
|
-
return array;
|
|
11
|
-
}
|
|
12
|
-
return [...array].sort(
|
|
13
|
-
(a, b) => this.compareSortablePrimitives(a, b, ascending)
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Checks if an array can be sorted as primitives
|
|
18
|
-
*/
|
|
19
|
-
static canSortPrimitiveArray(array) {
|
|
20
|
-
return this.allItemsAreSortablePrimitives(array) && this.allItemsHaveSameSortableType(array);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Checks if all items are sortable primitives
|
|
24
|
-
*/
|
|
25
|
-
static allItemsAreSortablePrimitives(array) {
|
|
26
|
-
return array.every((item) => this.isSortablePrimitive(item));
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Checks if all items have the same sortable type
|
|
30
|
-
*/
|
|
31
|
-
static allItemsHaveSameSortableType(array) {
|
|
32
|
-
if (array.length === 0) return true;
|
|
33
|
-
if (this.hasNullishValues(array)) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
const firstItem = array[0];
|
|
37
|
-
if (!this.isSortablePrimitive(firstItem)) return false;
|
|
38
|
-
const firstType = typeof firstItem;
|
|
39
|
-
return array.every((item) => typeof item === firstType);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Compares two sortable primitive values
|
|
43
|
-
*/
|
|
44
|
-
static compareSortablePrimitives(a, b, ascending) {
|
|
45
|
-
if (typeof a === "string" && typeof b === "string") {
|
|
46
|
-
return this.compareStrings(a, b, ascending);
|
|
47
|
-
}
|
|
48
|
-
if (typeof a === "number" && typeof b === "number") {
|
|
49
|
-
return this.compareNumbers(a, b, ascending);
|
|
50
|
-
}
|
|
51
|
-
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
52
|
-
return this.compareBooleans(a, b, ascending);
|
|
53
|
-
}
|
|
54
|
-
return 0;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Compares two strings
|
|
58
|
-
*/
|
|
59
|
-
static compareStrings(a, b, ascending) {
|
|
60
|
-
const result = a.localeCompare(b);
|
|
61
|
-
return ascending ? result : -result;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Compares two numbers
|
|
65
|
-
*/
|
|
66
|
-
static compareNumbers(a, b, ascending) {
|
|
67
|
-
if (isNaN(a) && isNaN(b)) return 0;
|
|
68
|
-
if (isNaN(a)) return 1;
|
|
69
|
-
if (isNaN(b)) return -1;
|
|
70
|
-
const result = a - b;
|
|
71
|
-
return ascending ? result : -result;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Compares two booleans
|
|
75
|
-
*/
|
|
76
|
-
static compareBooleans(a, b, ascending) {
|
|
77
|
-
if (a === b) return 0;
|
|
78
|
-
if (a) return ascending ? 1 : -1;
|
|
79
|
-
return ascending ? -1 : 1;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Checks if a value is a sortable primitive
|
|
83
|
-
*/
|
|
84
|
-
static isSortablePrimitive(value) {
|
|
85
|
-
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
86
|
-
typeof value
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Checks if an array has nullish values
|
|
91
|
-
*/
|
|
92
|
-
static hasNullishValues(array) {
|
|
93
|
-
return array.some(this.isNullish);
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Checks if a value is nullish
|
|
97
|
-
*/
|
|
98
|
-
static isNullish(value) {
|
|
99
|
-
return NULLISH_VALUES.includes(value);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
3
|
+
var SORTABLE_PRIMITIVE_TYPES = [
|
|
4
|
+
"string",
|
|
5
|
+
"number",
|
|
6
|
+
"boolean"
|
|
7
|
+
];
|
|
102
8
|
|
|
103
9
|
// src/type-guards.ts
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
* Checks if a value is a primitive (string, number, boolean, null, undefined)
|
|
107
|
-
*/
|
|
108
|
-
static isPrimitive(data) {
|
|
109
|
-
return this.isSortablePrimitive(data) || this.isNullish(data);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Checks if a value is a sortable primitive (string, number, boolean)
|
|
113
|
-
*/
|
|
114
|
-
static isSortablePrimitive(value) {
|
|
115
|
-
const sortableTypes = ["string", "number", "boolean"];
|
|
116
|
-
return sortableTypes.includes(typeof value);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Checks if a value is nullish (null or undefined)
|
|
120
|
-
*/
|
|
121
|
-
static isNullish(value) {
|
|
122
|
-
return value === null || value === void 0;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Checks if a value is a plain object
|
|
126
|
-
*/
|
|
127
|
-
static isObject(data) {
|
|
128
|
-
return typeof data === "object" && data !== null;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Checks if an object is non-sortable (Date, RegExp, Function, etc.)
|
|
132
|
-
*/
|
|
133
|
-
static isNonSortableObject(obj) {
|
|
134
|
-
const nonSortableTypes = [
|
|
135
|
-
Date,
|
|
136
|
-
RegExp,
|
|
137
|
-
Function,
|
|
138
|
-
Error,
|
|
139
|
-
Map,
|
|
140
|
-
Set,
|
|
141
|
-
WeakMap,
|
|
142
|
-
WeakSet,
|
|
143
|
-
Promise
|
|
144
|
-
];
|
|
145
|
-
return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Checks if an array has nullish values
|
|
149
|
-
*/
|
|
150
|
-
static hasNullishValues(array) {
|
|
151
|
-
return array.some(this.isNullish);
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// src/index.ts
|
|
156
|
-
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
157
|
-
const options = { ascending, sortPrimitiveArrays };
|
|
158
|
-
return sortRecursively(data, options);
|
|
10
|
+
function isNullish(value) {
|
|
11
|
+
return NULLISH_VALUES.includes(value);
|
|
159
12
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
13
|
+
function hasNullishValues(array) {
|
|
14
|
+
return array.some(isNullish);
|
|
15
|
+
}
|
|
16
|
+
function isSortablePrimitive(value) {
|
|
17
|
+
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
18
|
+
typeof value
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function isPrimitive(data) {
|
|
22
|
+
return isSortablePrimitive(data) || isNullish(data);
|
|
23
|
+
}
|
|
24
|
+
function isObject(data) {
|
|
25
|
+
return typeof data === "object" && data !== null;
|
|
26
|
+
}
|
|
27
|
+
function isNonSortableObject(obj) {
|
|
28
|
+
const nonSortableTypes = [
|
|
29
|
+
Date,
|
|
30
|
+
RegExp,
|
|
31
|
+
Function,
|
|
32
|
+
Error,
|
|
33
|
+
Map,
|
|
34
|
+
Set,
|
|
35
|
+
WeakMap,
|
|
36
|
+
WeakSet,
|
|
37
|
+
Promise
|
|
38
|
+
];
|
|
39
|
+
return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/comparators.ts
|
|
43
|
+
function compareSortablePrimitives(a, b, ascending) {
|
|
44
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
45
|
+
return ascending ? a.localeCompare(b) : b.localeCompare(a);
|
|
163
46
|
}
|
|
164
|
-
if (
|
|
165
|
-
return
|
|
47
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
48
|
+
return compareNumbers(a, b, ascending);
|
|
166
49
|
}
|
|
167
|
-
if (
|
|
168
|
-
return
|
|
50
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
51
|
+
return compareBooleans(a, b, ascending);
|
|
169
52
|
}
|
|
170
|
-
return
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
function compareNumbers(a, b, ascending) {
|
|
56
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return 0;
|
|
57
|
+
if (Number.isNaN(a)) return 1;
|
|
58
|
+
if (Number.isNaN(b)) return -1;
|
|
59
|
+
return ascending ? a - b : b - a;
|
|
60
|
+
}
|
|
61
|
+
function compareBooleans(a, b, ascending) {
|
|
62
|
+
if (a === b) return 0;
|
|
63
|
+
if (a) return ascending ? 1 : -1;
|
|
64
|
+
return ascending ? -1 : 1;
|
|
65
|
+
}
|
|
66
|
+
function compareObjectKeys(keyA, keyB, ascending) {
|
|
67
|
+
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
68
|
+
if (typeof keyA === "symbol") return 1;
|
|
69
|
+
if (typeof keyB === "symbol") return -1;
|
|
70
|
+
const stringA = keyA;
|
|
71
|
+
const stringB = keyB;
|
|
72
|
+
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
171
73
|
}
|
|
74
|
+
|
|
75
|
+
// src/array-sorter.ts
|
|
172
76
|
function sortArray(array, options) {
|
|
173
|
-
|
|
174
|
-
array,
|
|
175
|
-
options.ascending,
|
|
176
|
-
options.sortPrimitiveArrays
|
|
177
|
-
);
|
|
178
|
-
if (sortedPrimitiveArray !== array) {
|
|
179
|
-
return sortedPrimitiveArray;
|
|
77
|
+
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
|
|
78
|
+
return sortPrimitiveArray(array, options.ascending);
|
|
180
79
|
}
|
|
181
80
|
return array.map((item) => sortRecursively(item, options));
|
|
182
81
|
}
|
|
82
|
+
function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
|
|
83
|
+
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
84
|
+
}
|
|
85
|
+
function canSortPrimitiveArray(array) {
|
|
86
|
+
return allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array);
|
|
87
|
+
}
|
|
88
|
+
function allItemsAreSortablePrimitives(array) {
|
|
89
|
+
return array.every(isSortablePrimitive);
|
|
90
|
+
}
|
|
91
|
+
function sortPrimitiveArray(array, ascending) {
|
|
92
|
+
return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
|
|
93
|
+
}
|
|
94
|
+
function allItemsHaveSameSortableType(array) {
|
|
95
|
+
if (array.length === 0) return true;
|
|
96
|
+
if (hasNullishValues(array)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const firstItem = array[0];
|
|
100
|
+
if (!isSortablePrimitive(firstItem)) return false;
|
|
101
|
+
const expectedType = typeof firstItem;
|
|
102
|
+
const allSameType = array.every((item) => typeof item === expectedType);
|
|
103
|
+
if (allSameType) return true;
|
|
104
|
+
return array.every(isSortablePrimitive);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/object-sorter.ts
|
|
183
108
|
function sortObject(obj, options) {
|
|
184
109
|
const entries = collectObjectEntries(obj);
|
|
185
110
|
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
@@ -197,14 +122,6 @@ function sortObjectEntries(entries, ascending) {
|
|
|
197
122
|
([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
|
|
198
123
|
);
|
|
199
124
|
}
|
|
200
|
-
function compareObjectKeys(keyA, keyB, ascending) {
|
|
201
|
-
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
202
|
-
if (typeof keyA === "symbol") return 1;
|
|
203
|
-
if (typeof keyB === "symbol") return -1;
|
|
204
|
-
const stringA = keyA;
|
|
205
|
-
const stringB = keyB;
|
|
206
|
-
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
207
|
-
}
|
|
208
125
|
function createSortedObject(entries, options) {
|
|
209
126
|
const sortedEntries = entries.map(([key, value]) => [
|
|
210
127
|
key,
|
|
@@ -212,6 +129,26 @@ function createSortedObject(entries, options) {
|
|
|
212
129
|
]);
|
|
213
130
|
return Object.fromEntries(sortedEntries);
|
|
214
131
|
}
|
|
132
|
+
|
|
133
|
+
// src/core.ts
|
|
134
|
+
function sortRecursively(data, options) {
|
|
135
|
+
if (Array.isArray(data)) {
|
|
136
|
+
return sortArray(data, options);
|
|
137
|
+
}
|
|
138
|
+
if (isPrimitive(data) || isNonSortableObject(data)) {
|
|
139
|
+
return data;
|
|
140
|
+
}
|
|
141
|
+
if (isObject(data)) {
|
|
142
|
+
return sortObject(data, options);
|
|
143
|
+
}
|
|
144
|
+
return data;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/index.ts
|
|
148
|
+
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
149
|
+
const options = { ascending, sortPrimitiveArrays };
|
|
150
|
+
return sortRecursively(data, options);
|
|
151
|
+
}
|
|
215
152
|
export {
|
|
216
153
|
sort
|
|
217
154
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SortOptions } from './types';
|
|
2
|
+
import { isSortablePrimitive, hasNullishValues } from './type-guards';
|
|
3
|
+
import { compareSortablePrimitives } from './comparators';
|
|
4
|
+
|
|
5
|
+
export function sortArray<T>(array: T[], options: SortOptions): T[] {
|
|
6
|
+
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
|
|
7
|
+
return sortPrimitiveArray(array, options.ascending) as T[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return array.map((item) => sortRecursively(item, options));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function shouldSortPrimitiveArray(
|
|
14
|
+
array: unknown[],
|
|
15
|
+
sortPrimitiveArrays: boolean
|
|
16
|
+
): boolean {
|
|
17
|
+
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function canSortPrimitiveArray(array: unknown[]): boolean {
|
|
21
|
+
return (
|
|
22
|
+
allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function allItemsAreSortablePrimitives(array: unknown[]): boolean {
|
|
27
|
+
return array.every(isSortablePrimitive);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sortPrimitiveArray(array: unknown[], ascending: boolean): unknown[] {
|
|
31
|
+
// The array has already been validated as sortable in canSortPrimitiveArray
|
|
32
|
+
// No need to check allItemsHaveSameSortableType again
|
|
33
|
+
return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function allItemsHaveSameSortableType(array: unknown[]): boolean {
|
|
37
|
+
if (array.length === 0) return true;
|
|
38
|
+
|
|
39
|
+
// Don't sort arrays that contain null or undefined values
|
|
40
|
+
// as they represent absence of value and don't have a natural ordering
|
|
41
|
+
if (hasNullishValues(array)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For arrays with only sortable primitives, check if they have the same type
|
|
46
|
+
const firstItem = array[0];
|
|
47
|
+
if (!isSortablePrimitive(firstItem)) return false;
|
|
48
|
+
|
|
49
|
+
const expectedType = typeof firstItem;
|
|
50
|
+
const allSameType = array.every((item) => typeof item === expectedType);
|
|
51
|
+
|
|
52
|
+
// If all items are the same type, we can sort them normally
|
|
53
|
+
if (allSameType) return true;
|
|
54
|
+
|
|
55
|
+
// If we have mixed primitive types, we can still "sort" them
|
|
56
|
+
// (the comparison function will return 0 for different types, maintaining order)
|
|
57
|
+
// This allows us to cover the fallback case in compareSortablePrimitives
|
|
58
|
+
return array.every(isSortablePrimitive);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
import { sortRecursively } from './core';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Comparison functions for different data types
|
|
2
|
+
export function compareSortablePrimitives(
|
|
3
|
+
a: unknown,
|
|
4
|
+
b: unknown,
|
|
5
|
+
ascending: boolean
|
|
6
|
+
): number {
|
|
7
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
8
|
+
return ascending ? a.localeCompare(b) : b.localeCompare(a);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
12
|
+
return compareNumbers(a, b, ascending);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof a === 'boolean' && typeof b === 'boolean') {
|
|
16
|
+
return compareBooleans(a, b, ascending);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return 0; // Maintain order for other primitives
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function compareNumbers(
|
|
23
|
+
a: number,
|
|
24
|
+
b: number,
|
|
25
|
+
ascending: boolean
|
|
26
|
+
): number {
|
|
27
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return 0;
|
|
28
|
+
if (Number.isNaN(a)) return 1;
|
|
29
|
+
if (Number.isNaN(b)) return -1;
|
|
30
|
+
|
|
31
|
+
return ascending ? a - b : b - a;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function compareBooleans(
|
|
35
|
+
a: boolean,
|
|
36
|
+
b: boolean,
|
|
37
|
+
ascending: boolean
|
|
38
|
+
): number {
|
|
39
|
+
if (a === b) return 0;
|
|
40
|
+
if (a) return ascending ? 1 : -1;
|
|
41
|
+
return ascending ? -1 : 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function compareObjectKeys(
|
|
45
|
+
keyA: string | symbol,
|
|
46
|
+
keyB: string | symbol,
|
|
47
|
+
ascending: boolean
|
|
48
|
+
): number {
|
|
49
|
+
if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
|
|
50
|
+
if (typeof keyA === 'symbol') return 1;
|
|
51
|
+
if (typeof keyB === 'symbol') return -1;
|
|
52
|
+
|
|
53
|
+
const stringA = keyA as string;
|
|
54
|
+
const stringB = keyB as string;
|
|
55
|
+
|
|
56
|
+
return ascending
|
|
57
|
+
? stringA.localeCompare(stringB)
|
|
58
|
+
: stringB.localeCompare(stringA);
|
|
59
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SortOptions } from './types';
|
|
2
|
+
import { isPrimitive, isNonSortableObject, isObject } from './type-guards';
|
|
3
|
+
import { sortArray } from './array-sorter';
|
|
4
|
+
import { sortObject } from './object-sorter';
|
|
5
|
+
|
|
6
|
+
export function sortRecursively<T>(data: T, options: SortOptions): T {
|
|
7
|
+
if (Array.isArray(data)) {
|
|
8
|
+
return sortArray(data, options) as T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (isPrimitive(data) || isNonSortableObject(data)) {
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (isObject(data)) {
|
|
16
|
+
return sortObject(data, options) as T;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return data;
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
// Type definitions
|
|
5
|
-
type ObjectType = Record<string | symbol, unknown>;
|
|
6
|
-
type SortedEntry = [string | symbol, unknown];
|
|
7
|
-
|
|
8
|
-
type SortOptions = {
|
|
9
|
-
ascending: boolean;
|
|
10
|
-
sortPrimitiveArrays: boolean;
|
|
11
|
-
};
|
|
1
|
+
import { SortOptions } from './types';
|
|
2
|
+
import { sortRecursively } from './core';
|
|
12
3
|
|
|
13
4
|
export function sort<T>(
|
|
14
5
|
data: T,
|
|
@@ -19,87 +10,10 @@ export function sort<T>(
|
|
|
19
10
|
return sortRecursively(data, options);
|
|
20
11
|
}
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (TypeGuards.isObject(data)) {
|
|
32
|
-
return sortObject(data, options) as T;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return data;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function sortArray<T>(array: T[], options: SortOptions): T[] {
|
|
39
|
-
// Use the extracted PrimitiveArraySorter
|
|
40
|
-
const sortedPrimitiveArray = PrimitiveArraySorter.sortPrimitiveArray(
|
|
41
|
-
array,
|
|
42
|
-
options.ascending,
|
|
43
|
-
options.sortPrimitiveArrays
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (sortedPrimitiveArray !== array) {
|
|
47
|
-
return sortedPrimitiveArray as T[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Recursively sort nested items
|
|
51
|
-
return array.map((item) => sortRecursively(item, options));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function sortObject(obj: ObjectType, options: SortOptions): ObjectType {
|
|
55
|
-
const entries = collectObjectEntries(obj);
|
|
56
|
-
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
57
|
-
return createSortedObject(sortedEntries, options);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function collectObjectEntries(obj: ObjectType): SortedEntry[] {
|
|
61
|
-
const stringEntries = Object.entries(obj);
|
|
62
|
-
const symbolEntries = Object.getOwnPropertySymbols(obj).map(
|
|
63
|
-
(symbol) => [symbol, obj[symbol]] as SortedEntry
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
return [...stringEntries, ...symbolEntries];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function sortObjectEntries(
|
|
70
|
-
entries: SortedEntry[],
|
|
71
|
-
ascending: boolean
|
|
72
|
-
): SortedEntry[] {
|
|
73
|
-
return entries.sort(([keyA], [keyB]) =>
|
|
74
|
-
compareObjectKeys(keyA, keyB, ascending)
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function compareObjectKeys(
|
|
79
|
-
keyA: string | symbol,
|
|
80
|
-
keyB: string | symbol,
|
|
81
|
-
ascending: boolean
|
|
82
|
-
): number {
|
|
83
|
-
if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
|
|
84
|
-
if (typeof keyA === 'symbol') return 1;
|
|
85
|
-
if (typeof keyB === 'symbol') return -1;
|
|
86
|
-
|
|
87
|
-
const stringA = keyA as string;
|
|
88
|
-
const stringB = keyB as string;
|
|
89
|
-
|
|
90
|
-
return ascending
|
|
91
|
-
? stringA.localeCompare(stringB)
|
|
92
|
-
: stringB.localeCompare(stringA);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function createSortedObject(
|
|
96
|
-
entries: SortedEntry[],
|
|
97
|
-
options: SortOptions
|
|
98
|
-
): ObjectType {
|
|
99
|
-
const sortedEntries = entries.map(([key, value]) => [
|
|
100
|
-
key,
|
|
101
|
-
sortRecursively(value, options),
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
return Object.fromEntries(sortedEntries);
|
|
105
|
-
}
|
|
13
|
+
// Re-export types for convenience
|
|
14
|
+
export type {
|
|
15
|
+
SortOptions,
|
|
16
|
+
ObjectType,
|
|
17
|
+
SortedEntry,
|
|
18
|
+
NonSortableType,
|
|
19
|
+
} from './types';
|
package/src/object-sorter.ts
CHANGED
|
@@ -1,72 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
type SortedEntry = [string | symbol, unknown];
|
|
1
|
+
import { SortOptions, ObjectType, SortedEntry } from './types';
|
|
2
|
+
import { compareObjectKeys } from './comparators';
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
obj: ObjectType,
|
|
11
|
-
options: { ascending: boolean }
|
|
12
|
-
): ObjectType {
|
|
13
|
-
const entries = this.collectObjectEntries(obj);
|
|
14
|
-
const sortedEntries = this.sortObjectEntries(entries, options.ascending);
|
|
15
|
-
return this.createSortedObject(sortedEntries);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Collects all object entries (string and symbol keys)
|
|
20
|
-
*/
|
|
21
|
-
private static collectObjectEntries(obj: ObjectType): SortedEntry[] {
|
|
22
|
-
const stringEntries = Object.entries(obj);
|
|
23
|
-
const symbolEntries = Object.getOwnPropertySymbols(obj).map(
|
|
24
|
-
(symbol) => [symbol, obj[symbol]] as SortedEntry
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
return [...stringEntries, ...symbolEntries];
|
|
28
|
-
}
|
|
4
|
+
export function sortObject(obj: ObjectType, options: SortOptions): ObjectType {
|
|
5
|
+
const entries = collectObjectEntries(obj);
|
|
6
|
+
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
7
|
+
return createSortedObject(sortedEntries, options);
|
|
8
|
+
}
|
|
29
9
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
): SortedEntry[] {
|
|
37
|
-
return entries.sort(([keyA], [keyB]) =>
|
|
38
|
-
this.compareObjectKeys(keyA, keyB, ascending)
|
|
39
|
-
);
|
|
40
|
-
}
|
|
10
|
+
// Object entry handling
|
|
11
|
+
function collectObjectEntries(obj: ObjectType): SortedEntry[] {
|
|
12
|
+
const stringEntries = Object.entries(obj);
|
|
13
|
+
const symbolEntries = Object.getOwnPropertySymbols(obj).map(
|
|
14
|
+
(symbol) => [symbol, obj[symbol]] as SortedEntry
|
|
15
|
+
);
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
45
|
-
private static compareObjectKeys(
|
|
46
|
-
keyA: string | symbol,
|
|
47
|
-
keyB: string | symbol,
|
|
48
|
-
ascending: boolean
|
|
49
|
-
): number {
|
|
50
|
-
// Symbols are always sorted after strings
|
|
51
|
-
if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
|
|
52
|
-
if (typeof keyA === 'symbol') return 1;
|
|
53
|
-
if (typeof keyB === 'symbol') return -1;
|
|
17
|
+
return [...stringEntries, ...symbolEntries];
|
|
18
|
+
}
|
|
54
19
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
20
|
+
function sortObjectEntries(
|
|
21
|
+
entries: SortedEntry[],
|
|
22
|
+
ascending: boolean
|
|
23
|
+
): SortedEntry[] {
|
|
24
|
+
return entries.sort(([keyA], [keyB]) =>
|
|
25
|
+
compareObjectKeys(keyA, keyB, ascending)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
58
28
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
29
|
+
function createSortedObject(
|
|
30
|
+
entries: SortedEntry[],
|
|
31
|
+
options: SortOptions
|
|
32
|
+
): ObjectType {
|
|
33
|
+
const sortedEntries = entries.map(([key, value]) => [
|
|
34
|
+
key,
|
|
35
|
+
sortRecursively(value, options),
|
|
36
|
+
]);
|
|
63
37
|
|
|
64
|
-
|
|
65
|
-
* Creates a new object with sorted entries
|
|
66
|
-
*/
|
|
67
|
-
private static createSortedObject(entries: SortedEntry[]): ObjectType {
|
|
68
|
-
// Note: This is a placeholder - the actual implementation will be in the main file
|
|
69
|
-
// since it needs access to the recursive sorting function
|
|
70
|
-
return Object.fromEntries(entries);
|
|
71
|
-
}
|
|
38
|
+
return Object.fromEntries(sortedEntries);
|
|
72
39
|
}
|
|
40
|
+
|
|
41
|
+
import { sortRecursively } from './core';
|
package/src/type-guards.ts
CHANGED
|
@@ -1,74 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
| Map<unknown, unknown>
|
|
9
|
-
| Set<unknown>
|
|
10
|
-
| WeakMap<object, unknown>
|
|
11
|
-
| WeakSet<object>
|
|
12
|
-
| Promise<unknown>;
|
|
1
|
+
import {
|
|
2
|
+
NULLISH_VALUES,
|
|
3
|
+
SORTABLE_PRIMITIVE_TYPES,
|
|
4
|
+
NullishValue,
|
|
5
|
+
NonSortableType,
|
|
6
|
+
ObjectType,
|
|
7
|
+
} from './types';
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
static isPrimitive(data: unknown): boolean {
|
|
19
|
-
return this.isSortablePrimitive(data) || this.isNullish(data);
|
|
20
|
-
}
|
|
9
|
+
// Type guards - single responsibility, clear naming
|
|
10
|
+
export function isNullish(value: unknown): value is NullishValue {
|
|
11
|
+
return NULLISH_VALUES.includes(value as NullishValue);
|
|
12
|
+
}
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
static isSortablePrimitive(
|
|
26
|
-
value: unknown
|
|
27
|
-
): value is string | number | boolean {
|
|
28
|
-
const sortableTypes = ['string', 'number', 'boolean'];
|
|
29
|
-
return sortableTypes.includes(typeof value);
|
|
30
|
-
}
|
|
14
|
+
export function hasNullishValues(array: unknown[]): boolean {
|
|
15
|
+
return array.some(isNullish);
|
|
16
|
+
}
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
export function isSortablePrimitive(
|
|
19
|
+
value: unknown
|
|
20
|
+
): value is string | number | boolean {
|
|
21
|
+
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
22
|
+
typeof value as 'string' | 'number' | 'boolean'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
38
25
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
static isObject(data: unknown): data is Record<string | symbol, unknown> {
|
|
43
|
-
return typeof data === 'object' && data !== null;
|
|
44
|
-
}
|
|
26
|
+
export function isPrimitive(data: unknown): boolean {
|
|
27
|
+
return isSortablePrimitive(data) || isNullish(data);
|
|
28
|
+
}
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
static isNonSortableObject(obj: unknown): obj is NonSortableType {
|
|
50
|
-
const nonSortableTypes = [
|
|
51
|
-
Date,
|
|
52
|
-
RegExp,
|
|
53
|
-
Function,
|
|
54
|
-
Error,
|
|
55
|
-
Map,
|
|
56
|
-
Set,
|
|
57
|
-
WeakMap,
|
|
58
|
-
WeakSet,
|
|
59
|
-
Promise,
|
|
60
|
-
];
|
|
30
|
+
export function isObject(data: unknown): data is ObjectType {
|
|
31
|
+
return typeof data === 'object' && data !== null;
|
|
32
|
+
}
|
|
61
33
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
export function isNonSortableObject(obj: unknown): obj is NonSortableType {
|
|
35
|
+
const nonSortableTypes = [
|
|
36
|
+
Date,
|
|
37
|
+
RegExp,
|
|
38
|
+
Function,
|
|
39
|
+
Error,
|
|
40
|
+
Map,
|
|
41
|
+
Set,
|
|
42
|
+
WeakMap,
|
|
43
|
+
WeakSet,
|
|
44
|
+
Promise,
|
|
45
|
+
];
|
|
67
46
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return array.some(this.isNullish);
|
|
73
|
-
}
|
|
47
|
+
return (
|
|
48
|
+
nonSortableTypes.some((type) => obj instanceof type) ||
|
|
49
|
+
Symbol.iterator in Object(obj)
|
|
50
|
+
);
|
|
74
51
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Constants - make the code self-documenting
|
|
2
|
+
export const NULLISH_VALUES = [null, undefined] as const;
|
|
3
|
+
export const SORTABLE_PRIMITIVE_TYPES = [
|
|
4
|
+
'string',
|
|
5
|
+
'number',
|
|
6
|
+
'boolean',
|
|
7
|
+
] as const;
|
|
8
|
+
|
|
9
|
+
// Type definitions that make sense
|
|
10
|
+
export type NullishValue = (typeof NULLISH_VALUES)[number];
|
|
11
|
+
export type SortablePrimitiveType = (typeof SORTABLE_PRIMITIVE_TYPES)[number];
|
|
12
|
+
export type ObjectType = Record<string | symbol, unknown>;
|
|
13
|
+
export type SortedEntry = [string | symbol, unknown];
|
|
14
|
+
export type NonSortableType =
|
|
15
|
+
| Date
|
|
16
|
+
| RegExp
|
|
17
|
+
| (() => unknown)
|
|
18
|
+
| ((...args: unknown[]) => unknown)
|
|
19
|
+
| Error
|
|
20
|
+
| Map<unknown, unknown>
|
|
21
|
+
| Set<unknown>
|
|
22
|
+
| WeakMap<object, unknown>
|
|
23
|
+
| WeakSet<object>
|
|
24
|
+
| Promise<unknown>;
|
|
25
|
+
|
|
26
|
+
export type SortOptions = {
|
|
27
|
+
ascending: boolean;
|
|
28
|
+
sortPrimitiveArrays: boolean;
|
|
29
|
+
};
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
// Constants for primitive array sorting
|
|
2
|
-
const SORTABLE_PRIMITIVE_TYPES = ['string', 'number', 'boolean'] as const;
|
|
3
|
-
const NULLISH_VALUES = [null, undefined] as const;
|
|
4
|
-
|
|
5
|
-
// Type definitions
|
|
6
|
-
type SortablePrimitiveType = (typeof SORTABLE_PRIMITIVE_TYPES)[number];
|
|
7
|
-
type NullishValue = (typeof NULLISH_VALUES)[number];
|
|
8
|
-
|
|
9
|
-
export class PrimitiveArraySorter {
|
|
10
|
-
/**
|
|
11
|
-
* Sorts primitive arrays when enabled
|
|
12
|
-
*/
|
|
13
|
-
static sortPrimitiveArray(
|
|
14
|
-
array: unknown[],
|
|
15
|
-
ascending: boolean,
|
|
16
|
-
sortPrimitiveArrays: boolean
|
|
17
|
-
): unknown[] {
|
|
18
|
-
if (!sortPrimitiveArrays || !this.canSortPrimitiveArray(array)) {
|
|
19
|
-
return array;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return [...array].sort((a, b) =>
|
|
23
|
-
this.compareSortablePrimitives(a, b, ascending)
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Checks if an array can be sorted as primitives
|
|
29
|
-
*/
|
|
30
|
-
private static canSortPrimitiveArray(array: unknown[]): boolean {
|
|
31
|
-
return (
|
|
32
|
-
this.allItemsAreSortablePrimitives(array) &&
|
|
33
|
-
this.allItemsHaveSameSortableType(array)
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Checks if all items are sortable primitives
|
|
39
|
-
*/
|
|
40
|
-
private static allItemsAreSortablePrimitives(array: unknown[]): boolean {
|
|
41
|
-
return array.every((item) => this.isSortablePrimitive(item));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Checks if all items have the same sortable type
|
|
46
|
-
*/
|
|
47
|
-
private static allItemsHaveSameSortableType(array: unknown[]): boolean {
|
|
48
|
-
if (array.length === 0) return true;
|
|
49
|
-
|
|
50
|
-
// Don't sort arrays with null/undefined values
|
|
51
|
-
if (this.hasNullishValues(array)) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const firstItem = array[0];
|
|
56
|
-
if (!this.isSortablePrimitive(firstItem)) return false;
|
|
57
|
-
|
|
58
|
-
const firstType = typeof firstItem;
|
|
59
|
-
return array.every((item) => typeof item === firstType);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Compares two sortable primitive values
|
|
64
|
-
*/
|
|
65
|
-
private static compareSortablePrimitives(
|
|
66
|
-
a: unknown,
|
|
67
|
-
b: unknown,
|
|
68
|
-
ascending: boolean
|
|
69
|
-
): number {
|
|
70
|
-
if (typeof a === 'string' && typeof b === 'string') {
|
|
71
|
-
return this.compareStrings(a, b, ascending);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (typeof a === 'number' && typeof b === 'number') {
|
|
75
|
-
return this.compareNumbers(a, b, ascending);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof a === 'boolean' && typeof b === 'boolean') {
|
|
79
|
-
return this.compareBooleans(a, b, ascending);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Fallback for mixed types
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Compares two strings
|
|
88
|
-
*/
|
|
89
|
-
private static compareStrings(
|
|
90
|
-
a: string,
|
|
91
|
-
b: string,
|
|
92
|
-
ascending: boolean
|
|
93
|
-
): number {
|
|
94
|
-
const result = a.localeCompare(b);
|
|
95
|
-
return ascending ? result : -result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Compares two numbers
|
|
100
|
-
*/
|
|
101
|
-
private static compareNumbers(
|
|
102
|
-
a: number,
|
|
103
|
-
b: number,
|
|
104
|
-
ascending: boolean
|
|
105
|
-
): number {
|
|
106
|
-
if (isNaN(a) && isNaN(b)) return 0;
|
|
107
|
-
if (isNaN(a)) return 1; // NaN always goes to the end
|
|
108
|
-
if (isNaN(b)) return -1; // NaN always goes to the end
|
|
109
|
-
|
|
110
|
-
const result = a - b;
|
|
111
|
-
return ascending ? result : -result;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Compares two booleans
|
|
116
|
-
*/
|
|
117
|
-
private static compareBooleans(
|
|
118
|
-
a: boolean,
|
|
119
|
-
b: boolean,
|
|
120
|
-
ascending: boolean
|
|
121
|
-
): number {
|
|
122
|
-
if (a === b) return 0;
|
|
123
|
-
if (a) return ascending ? 1 : -1;
|
|
124
|
-
return ascending ? -1 : 1;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Checks if a value is a sortable primitive
|
|
129
|
-
*/
|
|
130
|
-
private static isSortablePrimitive(
|
|
131
|
-
value: unknown
|
|
132
|
-
): value is string | number | boolean {
|
|
133
|
-
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
134
|
-
typeof value as SortablePrimitiveType
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Checks if an array has nullish values
|
|
140
|
-
*/
|
|
141
|
-
private static hasNullishValues(array: unknown[]): boolean {
|
|
142
|
-
return array.some(this.isNullish);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Checks if a value is nullish
|
|
147
|
-
*/
|
|
148
|
-
private static isNullish(value: unknown): value is NullishValue {
|
|
149
|
-
return NULLISH_VALUES.includes(value as NullishValue);
|
|
150
|
-
}
|
|
151
|
-
}
|