@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 CHANGED
@@ -23,8 +23,162 @@ __export(index_exports, {
23
23
  sort: () => sort
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
- var NULLISH_VALUES = [null, void 0];
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
- if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
46
- return sortPrimitiveArray(array, options.ascending);
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/index.ts
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
- if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
22
- return sortPrimitiveArray(array, options.ascending);
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.1",
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": "^29.5.13",
36
- "@types/node": "^22.7.5",
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": "^29.7.0",
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
- // Constants - make the code self-documenting
2
- const NULLISH_VALUES = [null, undefined] as const;
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 that make sense
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
- if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
54
- return sortPrimitiveArray(array, options.ascending) as 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[];
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
+ }