@tamtamchik/json-deep-sort 1.4.0-beta.2 → 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 +94 -76
- package/dist/index.mjs +93 -77
- 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 -228
- package/src/object-sorter.ts +41 -0
- package/src/type-guards.ts +51 -0
- package/src/types.ts +29 -0
package/dist/index.js
CHANGED
|
@@ -23,35 +23,88 @@ __export(index_exports, {
|
|
|
23
23
|
sort: () => sort
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/types.ts
|
|
26
28
|
var NULLISH_VALUES = [null, void 0];
|
|
27
|
-
var SORTABLE_PRIMITIVE_TYPES = [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
var SORTABLE_PRIMITIVE_TYPES = [
|
|
30
|
+
"string",
|
|
31
|
+
"number",
|
|
32
|
+
"boolean"
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// src/type-guards.ts
|
|
36
|
+
function isNullish(value) {
|
|
37
|
+
return NULLISH_VALUES.includes(value);
|
|
31
38
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
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);
|
|
35
72
|
}
|
|
36
|
-
if (
|
|
37
|
-
return
|
|
73
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
74
|
+
return compareNumbers(a, b, ascending);
|
|
38
75
|
}
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
76
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
77
|
+
return compareBooleans(a, b, ascending);
|
|
41
78
|
}
|
|
42
|
-
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;
|
|
43
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);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/array-sorter.ts
|
|
44
102
|
function sortArray(array, options) {
|
|
45
103
|
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
|
|
46
104
|
return sortPrimitiveArray(array, options.ascending);
|
|
47
105
|
}
|
|
48
106
|
return array.map((item) => sortRecursively(item, options));
|
|
49
107
|
}
|
|
50
|
-
function sortObject(obj, options) {
|
|
51
|
-
const entries = collectObjectEntries(obj);
|
|
52
|
-
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
53
|
-
return createSortedObject(sortedEntries, options);
|
|
54
|
-
}
|
|
55
108
|
function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
|
|
56
109
|
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
57
110
|
}
|
|
@@ -76,28 +129,12 @@ function allItemsHaveSameSortableType(array) {
|
|
|
76
129
|
if (allSameType) return true;
|
|
77
130
|
return array.every(isSortablePrimitive);
|
|
78
131
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
132
|
+
|
|
133
|
+
// src/object-sorter.ts
|
|
134
|
+
function sortObject(obj, options) {
|
|
135
|
+
const entries = collectObjectEntries(obj);
|
|
136
|
+
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
137
|
+
return createSortedObject(sortedEntries, options);
|
|
101
138
|
}
|
|
102
139
|
function collectObjectEntries(obj) {
|
|
103
140
|
const stringEntries = Object.entries(obj);
|
|
@@ -111,14 +148,6 @@ function sortObjectEntries(entries, ascending) {
|
|
|
111
148
|
([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
|
|
112
149
|
);
|
|
113
150
|
}
|
|
114
|
-
function compareObjectKeys(keyA, keyB, ascending) {
|
|
115
|
-
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
116
|
-
if (typeof keyA === "symbol") return 1;
|
|
117
|
-
if (typeof keyB === "symbol") return -1;
|
|
118
|
-
const stringA = keyA;
|
|
119
|
-
const stringB = keyB;
|
|
120
|
-
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
121
|
-
}
|
|
122
151
|
function createSortedObject(entries, options) {
|
|
123
152
|
const sortedEntries = entries.map(([key, value]) => [
|
|
124
153
|
key,
|
|
@@ -126,36 +155,25 @@ function createSortedObject(entries, options) {
|
|
|
126
155
|
]);
|
|
127
156
|
return Object.fromEntries(sortedEntries);
|
|
128
157
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
function isObject(data) {
|
|
144
|
-
return typeof data === "object" && data !== null;
|
|
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;
|
|
145
171
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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);
|
|
172
|
+
|
|
173
|
+
// src/index.ts
|
|
174
|
+
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
175
|
+
const options = { ascending, sortPrimitiveArrays };
|
|
176
|
+
return sortRecursively(data, options);
|
|
159
177
|
}
|
|
160
178
|
// Annotate the CommonJS export names for ESM import in node:
|
|
161
179
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,33 +1,84 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/types.ts
|
|
2
2
|
var NULLISH_VALUES = [null, void 0];
|
|
3
|
-
var SORTABLE_PRIMITIVE_TYPES = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
var SORTABLE_PRIMITIVE_TYPES = [
|
|
4
|
+
"string",
|
|
5
|
+
"number",
|
|
6
|
+
"boolean"
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// src/type-guards.ts
|
|
10
|
+
function isNullish(value) {
|
|
11
|
+
return NULLISH_VALUES.includes(value);
|
|
7
12
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
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);
|
|
11
46
|
}
|
|
12
|
-
if (
|
|
13
|
-
return
|
|
47
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
48
|
+
return compareNumbers(a, b, ascending);
|
|
14
49
|
}
|
|
15
|
-
if (
|
|
16
|
-
return
|
|
50
|
+
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
51
|
+
return compareBooleans(a, b, ascending);
|
|
17
52
|
}
|
|
18
|
-
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;
|
|
19
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);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/array-sorter.ts
|
|
20
76
|
function sortArray(array, options) {
|
|
21
77
|
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
|
|
22
78
|
return sortPrimitiveArray(array, options.ascending);
|
|
23
79
|
}
|
|
24
80
|
return array.map((item) => sortRecursively(item, options));
|
|
25
81
|
}
|
|
26
|
-
function sortObject(obj, options) {
|
|
27
|
-
const entries = collectObjectEntries(obj);
|
|
28
|
-
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
29
|
-
return createSortedObject(sortedEntries, options);
|
|
30
|
-
}
|
|
31
82
|
function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
|
|
32
83
|
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
33
84
|
}
|
|
@@ -52,28 +103,12 @@ function allItemsHaveSameSortableType(array) {
|
|
|
52
103
|
if (allSameType) return true;
|
|
53
104
|
return array.every(isSortablePrimitive);
|
|
54
105
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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;
|
|
106
|
+
|
|
107
|
+
// src/object-sorter.ts
|
|
108
|
+
function sortObject(obj, options) {
|
|
109
|
+
const entries = collectObjectEntries(obj);
|
|
110
|
+
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
111
|
+
return createSortedObject(sortedEntries, options);
|
|
77
112
|
}
|
|
78
113
|
function collectObjectEntries(obj) {
|
|
79
114
|
const stringEntries = Object.entries(obj);
|
|
@@ -87,14 +122,6 @@ function sortObjectEntries(entries, ascending) {
|
|
|
87
122
|
([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
|
|
88
123
|
);
|
|
89
124
|
}
|
|
90
|
-
function compareObjectKeys(keyA, keyB, ascending) {
|
|
91
|
-
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
92
|
-
if (typeof keyA === "symbol") return 1;
|
|
93
|
-
if (typeof keyB === "symbol") return -1;
|
|
94
|
-
const stringA = keyA;
|
|
95
|
-
const stringB = keyB;
|
|
96
|
-
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
97
|
-
}
|
|
98
125
|
function createSortedObject(entries, options) {
|
|
99
126
|
const sortedEntries = entries.map(([key, value]) => [
|
|
100
127
|
key,
|
|
@@ -102,36 +129,25 @@ function createSortedObject(entries, options) {
|
|
|
102
129
|
]);
|
|
103
130
|
return Object.fromEntries(sortedEntries);
|
|
104
131
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
function isObject(data) {
|
|
120
|
-
return typeof data === "object" && data !== null;
|
|
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;
|
|
121
145
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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);
|
|
146
|
+
|
|
147
|
+
// src/index.ts
|
|
148
|
+
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
149
|
+
const options = { ascending, sortPrimitiveArrays };
|
|
150
|
+
return sortRecursively(data, options);
|
|
135
151
|
}
|
|
136
152
|
export {
|
|
137
153
|
sort
|
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,28 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const SORTABLE_PRIMITIVE_TYPES = ['string', 'number', 'boolean'] as const;
|
|
4
|
-
|
|
5
|
-
// Type definitions that make sense
|
|
6
|
-
type NullishValue = (typeof NULLISH_VALUES)[number];
|
|
7
|
-
type SortablePrimitiveType = (typeof SORTABLE_PRIMITIVE_TYPES)[number];
|
|
8
|
-
type ObjectType = Record<string | symbol, unknown>;
|
|
9
|
-
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
|
-
|
|
22
|
-
type SortOptions = {
|
|
23
|
-
ascending: boolean;
|
|
24
|
-
sortPrimitiveArrays: boolean;
|
|
25
|
-
};
|
|
1
|
+
import { SortOptions } from './types';
|
|
2
|
+
import { sortRecursively } from './core';
|
|
26
3
|
|
|
27
4
|
export function sort<T>(
|
|
28
5
|
data: T,
|
|
@@ -33,206 +10,10 @@ export function sort<T>(
|
|
|
33
10
|
return sortRecursively(data, options);
|
|
34
11
|
}
|
|
35
12
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (isObject(data)) {
|
|
46
|
-
return sortObject(data, options) as T;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return data;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function sortArray<T>(array: T[], options: SortOptions): T[] {
|
|
53
|
-
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
|
|
54
|
-
return sortPrimitiveArray(array, options.ascending) as T[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return array.map((item) => sortRecursively(item, options));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function sortObject(obj: ObjectType, options: SortOptions): ObjectType {
|
|
61
|
-
const entries = collectObjectEntries(obj);
|
|
62
|
-
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
63
|
-
return createSortedObject(sortedEntries, options);
|
|
64
|
-
}
|
|
65
|
-
|
|
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
|
-
function collectObjectEntries(obj: ObjectType): SortedEntry[] {
|
|
150
|
-
const stringEntries = Object.entries(obj);
|
|
151
|
-
const symbolEntries = Object.getOwnPropertySymbols(obj).map(
|
|
152
|
-
(symbol) => [symbol, obj[symbol]] as SortedEntry
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
return [...stringEntries, ...symbolEntries];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function sortObjectEntries(
|
|
159
|
-
entries: SortedEntry[],
|
|
160
|
-
ascending: boolean
|
|
161
|
-
): SortedEntry[] {
|
|
162
|
-
return entries.sort(([keyA], [keyB]) =>
|
|
163
|
-
compareObjectKeys(keyA, keyB, ascending)
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function compareObjectKeys(
|
|
168
|
-
keyA: string | symbol,
|
|
169
|
-
keyB: string | symbol,
|
|
170
|
-
ascending: boolean
|
|
171
|
-
): number {
|
|
172
|
-
if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
|
|
173
|
-
if (typeof keyA === 'symbol') return 1;
|
|
174
|
-
if (typeof keyB === 'symbol') return -1;
|
|
175
|
-
|
|
176
|
-
const stringA = keyA as string;
|
|
177
|
-
const stringB = keyB as string;
|
|
178
|
-
|
|
179
|
-
return ascending
|
|
180
|
-
? stringA.localeCompare(stringB)
|
|
181
|
-
: stringB.localeCompare(stringA);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function createSortedObject(
|
|
185
|
-
entries: SortedEntry[],
|
|
186
|
-
options: SortOptions
|
|
187
|
-
): ObjectType {
|
|
188
|
-
const sortedEntries = entries.map(([key, value]) => [
|
|
189
|
-
key,
|
|
190
|
-
sortRecursively(value, options),
|
|
191
|
-
]);
|
|
192
|
-
|
|
193
|
-
return Object.fromEntries(sortedEntries);
|
|
194
|
-
}
|
|
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
|
-
}
|
|
13
|
+
// Re-export types for convenience
|
|
14
|
+
export type {
|
|
15
|
+
SortOptions,
|
|
16
|
+
ObjectType,
|
|
17
|
+
SortedEntry,
|
|
18
|
+
NonSortableType,
|
|
19
|
+
} from './types';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SortOptions, ObjectType, SortedEntry } from './types';
|
|
2
|
+
import { compareObjectKeys } from './comparators';
|
|
3
|
+
|
|
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
|
+
}
|
|
9
|
+
|
|
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
|
+
);
|
|
16
|
+
|
|
17
|
+
return [...stringEntries, ...symbolEntries];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sortObjectEntries(
|
|
21
|
+
entries: SortedEntry[],
|
|
22
|
+
ascending: boolean
|
|
23
|
+
): SortedEntry[] {
|
|
24
|
+
return entries.sort(([keyA], [keyB]) =>
|
|
25
|
+
compareObjectKeys(keyA, keyB, ascending)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
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
|
+
]);
|
|
37
|
+
|
|
38
|
+
return Object.fromEntries(sortedEntries);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
import { sortRecursively } from './core';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NULLISH_VALUES,
|
|
3
|
+
SORTABLE_PRIMITIVE_TYPES,
|
|
4
|
+
NullishValue,
|
|
5
|
+
NonSortableType,
|
|
6
|
+
ObjectType,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
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
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hasNullishValues(array: unknown[]): boolean {
|
|
15
|
+
return array.some(isNullish);
|
|
16
|
+
}
|
|
17
|
+
|
|
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
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isPrimitive(data: unknown): boolean {
|
|
27
|
+
return isSortablePrimitive(data) || isNullish(data);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isObject(data: unknown): data is ObjectType {
|
|
31
|
+
return typeof data === 'object' && data !== null;
|
|
32
|
+
}
|
|
33
|
+
|
|
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
|
+
];
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
nonSortableTypes.some((type) => obj instanceof type) ||
|
|
49
|
+
Symbol.iterator in Object(obj)
|
|
50
|
+
);
|
|
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
|
+
};
|