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