@tamtamchik/json-deep-sort 1.3.0 → 1.4.0-beta.1

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/README.md CHANGED
@@ -50,15 +50,18 @@ console.log(sort(simpleData));
50
50
 
51
51
  // Example 2: Sorting a nested object
52
52
  const nestedData = {
53
- b: 'b',
54
- a: 'a',
55
- c: { d: 'd', c: 'c', a: 'a', b: 'b' }
53
+ b: 'b',
54
+ a: 'a',
55
+ c: { d: 'd', c: 'c', a: 'a', b: 'b' },
56
56
  };
57
57
  console.log(sort(nestedData));
58
58
  // Output: { a: 'a', b: 'b', c: { a: 'a', b: 'b', c: 'c', d: 'd' } }
59
59
 
60
60
  // Example 3: Sorting an array of objects
61
- const arrayData = [{ b: 'b', a: 'a' }, { d: 'd', c: 'c' }];
61
+ const arrayData = [
62
+ { b: 'b', a: 'a' },
63
+ { d: 'd', c: 'c' },
64
+ ];
62
65
  console.log(sort(arrayData));
63
66
  // Output: [{ a: 'a', b: 'b' }, { c: 'c', d: 'd' }]
64
67
 
@@ -69,14 +72,35 @@ console.log(sort(descendingData, false));
69
72
 
70
73
  // Example 5: Handling mixed data types
71
74
  const mixedData = {
72
- b: [3, 1, 2],
73
- a: { z: 'z', y: 'y' },
74
- c: new Date('2023-01-01')
75
+ b: [3, 1, 2],
76
+ a: { z: 'z', y: 'y' },
77
+ c: new Date('2023-01-01'),
75
78
  };
76
79
  console.log(sort(mixedData));
77
80
  // Output: { a: { y: 'y', z: 'z' }, b: [3, 1, 2], c: Date('2023-01-01') }
81
+
82
+ // Example 6: Sorting arrays of primitives
83
+ const primitiveArrayData = ['b', 'a', 'c'];
84
+ console.log(sort(primitiveArrayData, true, true));
85
+ // Output: ['a', 'b', 'c']
78
86
  ```
79
87
 
88
+ ## API Reference
89
+
90
+ ### `sort(data, ascending?, sortPrimitiveArrays?)`
91
+
92
+ Sorts a JSON object or array by keys in a specified order.
93
+
94
+ **Parameters:**
95
+
96
+ - `data` (required): The data to sort. Can be an object, array, or primitive value.
97
+ - `ascending` (optional): Boolean flag to determine sort order. Defaults to `true` (ascending).
98
+ - `sortPrimitiveArrays` (optional): Boolean flag to enable sorting of arrays containing primitive values (strings, numbers, booleans). Defaults to `false` to maintain backward compatibility.
99
+
100
+ **Returns:** The sorted data with the same structure and type as the input.
101
+
102
+ **Note:** When `sortPrimitiveArrays` is `false` (default), arrays of primitives are not modified, preserving the original behavior. When `true`, primitive arrays are sorted according to the `ascending` parameter.
103
+
80
104
  ## Contributing
81
105
 
82
106
  Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
@@ -92,7 +116,6 @@ JSON Deep Sort is [MIT licensed](./LICENSE).
92
116
  [ico-scrutinizer-build]: https://img.shields.io/scrutinizer/build/g/tamtamchik/json-deep-sort/main.svg?style=flat-square
93
117
  [ico-scrutinizer-quality]: https://img.shields.io/scrutinizer/quality/g/tamtamchik/json-deep-sort/main.svg?style=flat-square
94
118
  [ico-scrutinizer-coverage]: https://img.shields.io/scrutinizer/coverage/g/tamtamchik/json-deep-sort/main.svg?style=flat-square
95
-
96
119
  [link-coffee]: https://www.buymeacoffee.com/tamtamchik
97
120
  [link-npm]: https://www.npmjs.com/package/@tamtamchik/json-deep-sort
98
121
  [link-downloads]: https://www.npmjs.com/package/@tamtamchik/json-deep-sort
package/dist/index.js CHANGED
@@ -18,35 +18,143 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
23
  sort: () => sort
24
24
  });
25
- module.exports = __toCommonJS(src_exports);
26
- function sort(data, ascending = true) {
27
- return sortRecursively(data, ascending);
25
+ module.exports = __toCommonJS(index_exports);
26
+ var NULLISH_VALUES = [null, void 0];
27
+ var SORTABLE_PRIMITIVE_TYPES = ["string", "number", "boolean"];
28
+ function sort(data, ascending = true, sortPrimitiveArrays = false) {
29
+ const options = { ascending, sortPrimitiveArrays };
30
+ return sortRecursively(data, options);
28
31
  }
29
- function sortRecursively(data, ascending) {
30
- if (!isObject(data) || Array.isArray(data) || isNonSortableObject(data)) {
31
- return Array.isArray(data) ? data.map((item) => sortRecursively(item, ascending)) : data;
32
+ function sortRecursively(data, options) {
33
+ if (Array.isArray(data)) {
34
+ return sortArray(data, options);
32
35
  }
33
- return sortObject(data, ascending);
36
+ if (isPrimitive(data) || isNonSortableObject(data)) {
37
+ return data;
38
+ }
39
+ if (isObject(data)) {
40
+ return sortObject(data, options);
41
+ }
42
+ return data;
43
+ }
44
+ function sortArray(array, options) {
45
+ if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
46
+ return sortPrimitiveArray(array, options.ascending);
47
+ }
48
+ return array.map((item) => sortRecursively(item, options));
49
+ }
50
+ function sortObject(obj, options) {
51
+ const entries = collectObjectEntries(obj);
52
+ const sortedEntries = sortObjectEntries(entries, options.ascending);
53
+ return createSortedObject(sortedEntries, options);
54
+ }
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
+ function collectObjectEntries(obj) {
103
+ const stringEntries = Object.entries(obj);
104
+ const symbolEntries = Object.getOwnPropertySymbols(obj).map(
105
+ (symbol) => [symbol, obj[symbol]]
106
+ );
107
+ return [...stringEntries, ...symbolEntries];
108
+ }
109
+ function sortObjectEntries(entries, ascending) {
110
+ return entries.sort(
111
+ ([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
112
+ );
113
+ }
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
+ function createSortedObject(entries, options) {
123
+ const sortedEntries = entries.map(([key, value]) => [
124
+ key,
125
+ sortRecursively(value, options)
126
+ ]);
127
+ return Object.fromEntries(sortedEntries);
128
+ }
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);
34
142
  }
35
143
  function isObject(data) {
36
144
  return typeof data === "object" && data !== null;
37
145
  }
38
- function sortObject(obj, ascending) {
39
- const entries = [...Object.entries(obj), ...Object.getOwnPropertySymbols(obj).map((sym) => [sym, obj[sym]])];
40
- const sortedEntries = entries.sort(([keyA], [keyB]) => {
41
- if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
42
- if (typeof keyA === "symbol") return 1;
43
- if (typeof keyB === "symbol") return -1;
44
- return ascending ? keyA.localeCompare(keyB) : keyB.localeCompare(keyA);
45
- });
46
- return Object.fromEntries(sortedEntries.map(([key, value]) => [key, sortRecursively(value, ascending)]));
47
- }
48
146
  function isNonSortableObject(obj) {
49
- const nonSortableTypes = [Date, RegExp, Function, Error, Map, Set, WeakMap, WeakSet, Promise];
147
+ const nonSortableTypes = [
148
+ Date,
149
+ RegExp,
150
+ Function,
151
+ Error,
152
+ Map,
153
+ Set,
154
+ WeakMap,
155
+ WeakSet,
156
+ Promise
157
+ ];
50
158
  return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
51
159
  }
52
160
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -1,28 +1,136 @@
1
1
  // src/index.ts
2
- function sort(data, ascending = true) {
3
- return sortRecursively(data, ascending);
2
+ var NULLISH_VALUES = [null, void 0];
3
+ var SORTABLE_PRIMITIVE_TYPES = ["string", "number", "boolean"];
4
+ function sort(data, ascending = true, sortPrimitiveArrays = false) {
5
+ const options = { ascending, sortPrimitiveArrays };
6
+ return sortRecursively(data, options);
4
7
  }
5
- function sortRecursively(data, ascending) {
6
- if (!isObject(data) || Array.isArray(data) || isNonSortableObject(data)) {
7
- return Array.isArray(data) ? data.map((item) => sortRecursively(item, ascending)) : data;
8
+ function sortRecursively(data, options) {
9
+ if (Array.isArray(data)) {
10
+ return sortArray(data, options);
8
11
  }
9
- return sortObject(data, ascending);
12
+ if (isPrimitive(data) || isNonSortableObject(data)) {
13
+ return data;
14
+ }
15
+ if (isObject(data)) {
16
+ return sortObject(data, options);
17
+ }
18
+ return data;
19
+ }
20
+ function sortArray(array, options) {
21
+ if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
22
+ return sortPrimitiveArray(array, options.ascending);
23
+ }
24
+ return array.map((item) => sortRecursively(item, options));
25
+ }
26
+ function sortObject(obj, options) {
27
+ const entries = collectObjectEntries(obj);
28
+ const sortedEntries = sortObjectEntries(entries, options.ascending);
29
+ return createSortedObject(sortedEntries, options);
30
+ }
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
+ function collectObjectEntries(obj) {
79
+ const stringEntries = Object.entries(obj);
80
+ const symbolEntries = Object.getOwnPropertySymbols(obj).map(
81
+ (symbol) => [symbol, obj[symbol]]
82
+ );
83
+ return [...stringEntries, ...symbolEntries];
84
+ }
85
+ function sortObjectEntries(entries, ascending) {
86
+ return entries.sort(
87
+ ([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
88
+ );
89
+ }
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
+ function createSortedObject(entries, options) {
99
+ const sortedEntries = entries.map(([key, value]) => [
100
+ key,
101
+ sortRecursively(value, options)
102
+ ]);
103
+ return Object.fromEntries(sortedEntries);
104
+ }
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);
10
118
  }
11
119
  function isObject(data) {
12
120
  return typeof data === "object" && data !== null;
13
121
  }
14
- function sortObject(obj, ascending) {
15
- const entries = [...Object.entries(obj), ...Object.getOwnPropertySymbols(obj).map((sym) => [sym, obj[sym]])];
16
- const sortedEntries = entries.sort(([keyA], [keyB]) => {
17
- if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
18
- if (typeof keyA === "symbol") return 1;
19
- if (typeof keyB === "symbol") return -1;
20
- return ascending ? keyA.localeCompare(keyB) : keyB.localeCompare(keyA);
21
- });
22
- return Object.fromEntries(sortedEntries.map(([key, value]) => [key, sortRecursively(value, ascending)]));
23
- }
24
122
  function isNonSortableObject(obj) {
25
- const nonSortableTypes = [Date, RegExp, Function, Error, Map, Set, WeakMap, WeakSet, Promise];
123
+ const nonSortableTypes = [
124
+ Date,
125
+ RegExp,
126
+ Function,
127
+ Error,
128
+ Map,
129
+ Set,
130
+ WeakMap,
131
+ WeakSet,
132
+ Promise
133
+ ];
26
134
  return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
27
135
  }
28
136
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamtamchik/json-deep-sort",
3
- "version": "1.3.0",
3
+ "version": "1.4.0-beta.1",
4
4
  "description": "A comprehensive utility for sorting JSON objects by keys.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -34,16 +34,24 @@
34
34
  "devDependencies": {
35
35
  "@types/jest": "^29.5.13",
36
36
  "@types/node": "^22.7.5",
37
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
38
+ "@typescript-eslint/parser": "^8.39.0",
39
+ "eslint": "^9.33.0",
40
+ "eslint-config-prettier": "^10.1.8",
41
+ "eslint-plugin-prettier": "^5.5.4",
37
42
  "jest": "^29.7.0",
43
+ "jiti": "^2.5.1",
44
+ "prettier": "^3.6.2",
38
45
  "ts-jest": "^29.2.4",
46
+ "ts-node": "^10.9.2",
39
47
  "tsup": "^8.3.0",
40
48
  "typescript": "^5.6.3"
41
49
  },
42
50
  "exports": {
43
51
  ".": {
52
+ "types": "./src/index.ts",
44
53
  "require": "./dist/index.js",
45
- "import": "./dist/index.mjs",
46
- "types": "./src/index.ts"
54
+ "import": "./dist/index.mjs"
47
55
  }
48
56
  },
49
57
  "scripts": {
@@ -51,6 +59,9 @@
51
59
  "dev": "npm run build -- --watch src",
52
60
  "prepublishOnly": "npm run build",
53
61
  "test": "jest",
54
- "coverage": "jest --coverage"
62
+ "coverage": "jest --coverage",
63
+ "format": "prettier --write .",
64
+ "lint": "eslint . --ext .ts,.js",
65
+ "lint:fix": "eslint . --ext .ts,.js --fix"
55
66
  }
56
67
  }
package/src/index.ts CHANGED
@@ -1,43 +1,238 @@
1
- // Type definitions for object types and non-sortable types
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;
4
+
5
+ // Type definitions that make sense
6
+ type NullishValue = (typeof NULLISH_VALUES)[number];
7
+ type SortablePrimitiveType = (typeof SORTABLE_PRIMITIVE_TYPES)[number];
2
8
  type ObjectType = Record<string | symbol, unknown>;
3
9
  type SortedEntry = [string | symbol, unknown];
4
- type NonSortableType = Date | RegExp | Function | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<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
+ };
5
26
 
6
- // Main sorting function that can handle various data types
7
- export function sort<T>(data: T, ascending = true): T {
8
- return sortRecursively(data, ascending);
27
+ export function sort<T>(
28
+ data: T,
29
+ ascending = true,
30
+ sortPrimitiveArrays = false
31
+ ): T {
32
+ const options: SortOptions = { ascending, sortPrimitiveArrays };
33
+ return sortRecursively(data, options);
9
34
  }
10
35
 
11
- // Recursive function to sort nested structures
12
- function sortRecursively<T>(data: T, ascending: boolean): T {
13
- if (!isObject(data) || Array.isArray(data) || isNonSortableObject(data)) {
14
- return Array.isArray(data) ? data.map(item => sortRecursively(item, ascending)) as T : data;
36
+ function sortRecursively<T>(data: T, options: SortOptions): T {
37
+ if (Array.isArray(data)) {
38
+ return sortArray(data, options) as T;
39
+ }
40
+
41
+ if (isPrimitive(data) || isNonSortableObject(data)) {
42
+ return data;
15
43
  }
16
44
 
17
- return sortObject(data as ObjectType, ascending) as T;
45
+ if (isObject(data)) {
46
+ return sortObject(data, options) as T;
47
+ }
48
+
49
+ return data;
18
50
  }
19
51
 
20
- // Helper function to check if a value is an object
21
- function isObject(data: unknown): data is ObjectType {
22
- return typeof data === 'object' && data !== null;
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
+ );
23
77
  }
24
78
 
25
- // Function to sort object properties
26
- function sortObject(obj: ObjectType, ascending: boolean): ObjectType {
27
- const entries = [...Object.entries(obj), ...Object.getOwnPropertySymbols(obj).map(sym => [sym, obj[sym]] as SortedEntry)];
28
-
29
- const sortedEntries = entries.sort(([keyA], [keyB]) => {
30
- if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
31
- if (typeof keyA === 'symbol') return 1;
32
- if (typeof keyB === 'symbol') return -1;
33
- return ascending ? (keyA as string).localeCompare(keyB as string) : (keyB as string).localeCompare(keyA as string);
34
- });
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);
35
104
 
36
- return Object.fromEntries(sortedEntries.map(([key, value]) => [key, sortRecursively(value, ascending)]));
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;
37
219
  }
38
220
 
39
- // Helper function to check if an object is of a non-sortable type
40
221
  function isNonSortableObject(obj: unknown): obj is NonSortableType {
41
- const nonSortableTypes = [Date, RegExp, Function, Error, Map, Set, WeakMap, WeakSet, Promise];
42
- return nonSortableTypes.some(type => obj instanceof type) || Symbol.iterator in Object(obj);
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
+ );
43
238
  }