@tamtamchik/json-deep-sort 1.5.0 → 1.5.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 +188 -88
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +86 -141
- package/dist/index.mjs +85 -114
- package/package.json +30 -22
- package/src/array-sorter.ts +0 -61
- package/src/comparators.ts +0 -59
- package/src/core.ts +0 -20
- package/src/index.ts +0 -19
- package/src/object-sorter.ts +0 -41
- package/src/type-guards.ts +0 -46
- package/src/types.ts +0 -29
- package/tsconfig.json +0 -11
package/README.md
CHANGED
|
@@ -1,122 +1,222 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/tamtamchik/json-deep-sort/main/docs/assets/json-deep-sort-readme-banner.png" alt="JSON Deep Sort" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Recursive JSON key sorting for JavaScript and TypeScript.</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@tamtamchik/json-deep-sort"><img alt="Latest version on npm" src="https://img.shields.io/npm/v/@tamtamchik/json-deep-sort?style=flat-square"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@tamtamchik/json-deep-sort"><img alt="Monthly downloads" src="https://img.shields.io/npm/dm/@tamtamchik/json-deep-sort?style=flat-square"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@tamtamchik/json-deep-sort"><img alt="Total downloads" src="https://img.shields.io/npm/dt/@tamtamchik/json-deep-sort?style=flat-square"></a>
|
|
13
|
+
<a href="https://github.com/tamtamchik/json-deep-sort/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/tamtamchik/json-deep-sort/ci.yml?branch=main&style=flat-square&label=CI"></a>
|
|
14
|
+
<a href="https://scrutinizer-ci.com/g/tamtamchik/json-deep-sort/"><img alt="Scrutinizer build" src="https://img.shields.io/scrutinizer/build/g/tamtamchik/json-deep-sort/main?style=flat-square"></a>
|
|
15
|
+
<a href="https://scrutinizer-ci.com/g/tamtamchik/json-deep-sort/"><img alt="Scrutinizer quality" src="https://img.shields.io/scrutinizer/quality/g/tamtamchik/json-deep-sort/main?style=flat-square"></a>
|
|
16
|
+
<a href="https://scrutinizer-ci.com/g/tamtamchik/json-deep-sort/"><img alt="Code coverage" src="https://img.shields.io/scrutinizer/coverage/g/tamtamchik/json-deep-sort/main?style=flat-square"></a>
|
|
17
|
+
<a href="LICENSE"><img alt="License MIT" src="https://img.shields.io/badge/license-MIT-22c55e?style=flat-square"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
22
|
+
<a href="#usage">Usage</a> ·
|
|
23
|
+
<a href="#development-setup">Development Setup</a> ·
|
|
24
|
+
<a href="#documentation">Documentation</a> ·
|
|
25
|
+
<a href="#security-notes">Security Notes</a> ·
|
|
26
|
+
<a href="#support">Support</a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
JSON Deep Sort sorts object keys throughout nested objects and arrays without
|
|
30
|
+
mutating the input. It supports ascending and descending order, optional sorting
|
|
31
|
+
of primitive arrays, ES modules and CommonJS, and includes TypeScript types.
|
|
32
|
+
The library has no runtime dependencies.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
Install with npm:
|
|
24
37
|
|
|
25
38
|
```shell
|
|
26
39
|
npm install @tamtamchik/json-deep-sort
|
|
27
40
|
```
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
Or with yarn:
|
|
30
43
|
|
|
31
44
|
```shell
|
|
32
45
|
yarn add @tamtamchik/json-deep-sort
|
|
33
46
|
```
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Import JSON Deep Sort in your TypeScript file:
|
|
48
|
+
Sort an object:
|
|
38
49
|
|
|
39
50
|
```typescript
|
|
40
51
|
import { sort } from '@tamtamchik/json-deep-sort';
|
|
52
|
+
|
|
53
|
+
const data = { b: 2, a: { d: 4, c: 3 } };
|
|
54
|
+
console.log(sort(data));
|
|
55
|
+
// { a: { c: 3, d: 4 }, b: 2 }
|
|
41
56
|
```
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
Pass an object, array, or primitive value to `sort`. It returns the result
|
|
61
|
+
synchronously. Object keys are sorted in ascending order by default; array
|
|
62
|
+
items keep their order unless primitive array sorting is enabled.
|
|
63
|
+
|
|
64
|
+
The Quick Start example uses an ES module import. For CommonJS, use `require`:
|
|
65
|
+
|
|
66
|
+
### CommonJS
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
const { sort } = require('@tamtamchik/json-deep-sort');
|
|
70
|
+
|
|
71
|
+
console.log(sort({ b: 'b', a: 'a', c: 'c' }));
|
|
72
|
+
// { a: 'a', b: 'b', c: 'c' }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Nested Objects and Arrays
|
|
76
|
+
|
|
77
|
+
Objects inside arrays are sorted recursively, while the array items keep their
|
|
78
|
+
positions:
|
|
44
79
|
|
|
45
80
|
```typescript
|
|
46
|
-
|
|
47
|
-
const simpleData = { b: 'b', a: 'a', c: 'c' };
|
|
48
|
-
console.log(sort(simpleData));
|
|
49
|
-
// Output: { a: 'a', b: 'b', c: 'c' }
|
|
50
|
-
|
|
51
|
-
// Example 2: Sorting a nested object
|
|
52
|
-
const nestedData = {
|
|
53
|
-
b: 'b',
|
|
54
|
-
a: 'a',
|
|
55
|
-
c: { d: 'd', c: 'c', a: 'a', b: 'b' },
|
|
56
|
-
};
|
|
57
|
-
console.log(sort(nestedData));
|
|
58
|
-
// Output: { a: 'a', b: 'b', c: { a: 'a', b: 'b', c: 'c', d: 'd' } }
|
|
59
|
-
|
|
60
|
-
// Example 3: Sorting an array of objects
|
|
61
|
-
const arrayData = [
|
|
62
|
-
{ b: 'b', a: 'a' },
|
|
63
|
-
{ d: 'd', c: 'c' },
|
|
64
|
-
];
|
|
65
|
-
console.log(sort(arrayData));
|
|
66
|
-
// Output: [{ a: 'a', b: 'b' }, { c: 'c', d: 'd' }]
|
|
67
|
-
|
|
68
|
-
// Example 4: Sorting in descending order
|
|
69
|
-
const descendingData = { a: 'a', c: 'c', b: 'b' };
|
|
70
|
-
console.log(sort(descendingData, false));
|
|
71
|
-
// Output: { c: 'c', b: 'b', a: 'a' }
|
|
72
|
-
|
|
73
|
-
// Example 5: Handling mixed data types
|
|
74
|
-
const mixedData = {
|
|
81
|
+
const data = {
|
|
75
82
|
b: [3, 1, 2],
|
|
76
|
-
a:
|
|
77
|
-
|
|
83
|
+
a: [
|
|
84
|
+
{ b: 'b', a: 'a' },
|
|
85
|
+
{ d: 'd', c: 'c' },
|
|
86
|
+
],
|
|
78
87
|
};
|
|
79
|
-
console.log(sort(mixedData));
|
|
80
|
-
// Output: { a: { y: 'y', z: 'z' }, b: [3, 1, 2], c: Date('2023-01-01') }
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
console.log(sort(data));
|
|
90
|
+
// { a: [{ a: 'a', b: 'b' }, { c: 'c', d: 'd' }], b: [3, 1, 2] }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Descending Order
|
|
94
|
+
|
|
95
|
+
Set the second argument to `false` to sort keys in descending order at each level:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
console.log(sort({ a: 'a', c: 'c', b: 'b' }, false));
|
|
99
|
+
// { c: 'c', b: 'b', a: 'a' }
|
|
86
100
|
```
|
|
87
101
|
|
|
88
|
-
|
|
102
|
+
### Primitive Arrays
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
Set the third argument to `true` to sort arrays of strings, numbers, or booleans,
|
|
105
|
+
including arrays nested inside objects:
|
|
91
106
|
|
|
92
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
console.log(sort(['b', 'a', 'c'], true, true));
|
|
109
|
+
// ['a', 'b', 'c']
|
|
93
110
|
|
|
94
|
-
|
|
111
|
+
console.log(sort([3, 1, 2], false, true));
|
|
112
|
+
// [3, 2, 1]
|
|
95
113
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
console.log(sort({ values: [3, 1, 2] }, true, true));
|
|
115
|
+
// { values: [1, 2, 3] }
|
|
116
|
+
```
|
|
99
117
|
|
|
100
|
-
|
|
118
|
+
Numbers use numeric order, strings use `localeCompare`, and booleans sort as
|
|
119
|
+
`false` before `true` in ascending order. Arrays containing `null`, `undefined`,
|
|
120
|
+
or objects retain their item order. Arrays mixing strings, numbers, and booleans
|
|
121
|
+
also keep their original order.
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
### Preserved Values
|
|
124
|
+
|
|
125
|
+
Primitive values pass through unchanged. Values such as `Date`, `RegExp`,
|
|
126
|
+
functions, `Map`, and `Set` retain their original references:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const date = new Date('2023-01-01');
|
|
130
|
+
const result = sort({ b: date, a: { z: 'z', y: 'y' } });
|
|
131
|
+
|
|
132
|
+
console.log(result.a);
|
|
133
|
+
// { y: 'y', z: 'z' }
|
|
134
|
+
console.log(result.b === date);
|
|
135
|
+
// true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### API Reference
|
|
139
|
+
|
|
140
|
+
#### `sort<T>(data: T, ascending = true, sortPrimitiveArrays = false): T`
|
|
141
|
+
|
|
142
|
+
| Parameter | Default | Description |
|
|
143
|
+
| --------------------- | -------- | ----------------------------------------------------------- |
|
|
144
|
+
| `data` | Required | Object, array, or primitive value to sort. |
|
|
145
|
+
| `ascending` | `true` | Sort keys and eligible primitive arrays in ascending order. |
|
|
146
|
+
| `sortPrimitiveArrays` | `false` | Enable sorting of arrays containing primitive values. |
|
|
147
|
+
|
|
148
|
+
The return type is inferred from the input. Traversed objects and arrays are
|
|
149
|
+
rebuilt; preserved values keep their references. JavaScript enumeration rules
|
|
150
|
+
keep integer-index keys in numeric order, even when descending order is requested.
|
|
151
|
+
String key ordering uses `localeCompare` and can depend on the runtime locale.
|
|
152
|
+
|
|
153
|
+
## Development Setup
|
|
154
|
+
|
|
155
|
+
Use Node.js 22 (22.18 or later), Node.js 24 (24.11 or later), or Node.js 26.
|
|
156
|
+
The `.nvmrc` file selects Node.js 24; npm enforces these development versions
|
|
157
|
+
through `devEngines`. CI tests Node.js 22, 24, and 26. These requirements apply
|
|
158
|
+
to development tooling; the published library adds no Node.js engine restriction.
|
|
159
|
+
|
|
160
|
+
Install dependencies:
|
|
161
|
+
|
|
162
|
+
```shell
|
|
163
|
+
npm ci
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Run the checks used in CI:
|
|
167
|
+
|
|
168
|
+
```shell
|
|
169
|
+
npm run check
|
|
170
|
+
npm run typecheck
|
|
171
|
+
npm run build
|
|
172
|
+
npm test
|
|
173
|
+
npm run test:package
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Useful focused commands:
|
|
177
|
+
|
|
178
|
+
```shell
|
|
179
|
+
npm run fix # Format files and apply safe lint fixes with Biome.
|
|
180
|
+
npm run dev # Rebuild when source files change.
|
|
181
|
+
npm run coverage # Run unit tests and generate coverage reports.
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Biome checks TypeScript, JavaScript, and JSON. Markdown and YAML are reviewed
|
|
185
|
+
manually. Coverage runs deterministic unit tests; `test:package` installs a packed
|
|
186
|
+
archive in an isolated consumer and checks CommonJS, ES modules, TypeScript types,
|
|
187
|
+
and package contents.
|
|
188
|
+
|
|
189
|
+
## Documentation
|
|
190
|
+
|
|
191
|
+
- [npm package](https://www.npmjs.com/package/@tamtamchik/json-deep-sort): published versions and installation details.
|
|
192
|
+
- [API reference](#api-reference): arguments, defaults, and return values.
|
|
193
|
+
- [Source](src/index.ts): public function and exported TypeScript types.
|
|
194
|
+
- [Unit tests](test/sort.test.ts): nested objects, arrays, sort order, and preserved values.
|
|
195
|
+
- [Package tests](test/package.test.mjs): packed files, imports, and consumer types.
|
|
196
|
+
- [Security policy](SECURITY.md): vulnerability reporting.
|
|
197
|
+
- [CI](https://github.com/tamtamchik/json-deep-sort/actions/workflows/ci.yml): formatting, lint, build, and test results.
|
|
198
|
+
|
|
199
|
+
## Security Notes
|
|
200
|
+
|
|
201
|
+
Use acyclic JSON data. Circular references are unsupported, and very deep inputs
|
|
202
|
+
can exceed the JavaScript call stack. Sorting keys does not validate or sanitize
|
|
203
|
+
input, and locale-dependent ordering is unsuitable for cryptographic JSON
|
|
204
|
+
canonicalization.
|
|
205
|
+
|
|
206
|
+
Report vulnerabilities by emailing [yuri.tam.tkachenko@gmail.com](mailto:yuri.tam.tkachenko@gmail.com).
|
|
207
|
+
See [SECURITY.md](SECURITY.md) for the reporting policy.
|
|
103
208
|
|
|
104
209
|
## Contributing
|
|
105
210
|
|
|
106
|
-
Pull requests are welcome. For major changes,
|
|
211
|
+
Pull requests are welcome. For major changes, [open an issue](https://github.com/tamtamchik/json-deep-sort/issues)
|
|
212
|
+
first to discuss the proposal.
|
|
213
|
+
|
|
214
|
+
## Support
|
|
215
|
+
|
|
216
|
+
<p>
|
|
217
|
+
<a href="https://www.buymeacoffee.com/tamtamchik"><img alt="Buy me a coffee" src="https://img.shields.io/badge/Buy%20Me%20A-Coffee-6F4E37?style=flat-square&logo=buymeacoffee&logoColor=white"></a>
|
|
218
|
+
</p>
|
|
107
219
|
|
|
108
220
|
## License
|
|
109
221
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
[ico-coffee]: https://img.shields.io/badge/Buy%20Me%20A-Coffee-%236F4E37.svg?style=flat-square
|
|
113
|
-
[ico-version]: https://img.shields.io/npm/v/@tamtamchik/json-deep-sort.svg?style=flat-square
|
|
114
|
-
[ico-license]: https://img.shields.io/npm/l/@tamtamchik/json-deep-sort.svg?style=flat-square
|
|
115
|
-
[ico-downloads]: https://img.shields.io/npm/dt/@tamtamchik/json-deep-sort.svg?style=flat-square
|
|
116
|
-
[ico-scrutinizer-build]: https://img.shields.io/scrutinizer/build/g/tamtamchik/json-deep-sort/main.svg?style=flat-square
|
|
117
|
-
[ico-scrutinizer-quality]: https://img.shields.io/scrutinizer/quality/g/tamtamchik/json-deep-sort/main.svg?style=flat-square
|
|
118
|
-
[ico-scrutinizer-coverage]: https://img.shields.io/scrutinizer/coverage/g/tamtamchik/json-deep-sort/main.svg?style=flat-square
|
|
119
|
-
[link-coffee]: https://www.buymeacoffee.com/tamtamchik
|
|
120
|
-
[link-npm]: https://www.npmjs.com/package/@tamtamchik/json-deep-sort
|
|
121
|
-
[link-downloads]: https://www.npmjs.com/package/@tamtamchik/json-deep-sort
|
|
122
|
-
[link-scrutinizer]: https://scrutinizer-ci.com/g/tamtamchik/json-deep-sort/
|
|
222
|
+
[MIT](LICENSE).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type ObjectType = Record<string | symbol, unknown>;
|
|
3
|
+
type SortedEntry = [string | symbol, unknown];
|
|
4
|
+
type NonSortableType = Date | RegExp | (() => unknown) | ((...args: unknown[]) => unknown) | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown>;
|
|
5
|
+
type SortOptions = {
|
|
6
|
+
ascending: boolean;
|
|
7
|
+
sortPrimitiveArrays: boolean;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/index.d.ts
|
|
11
|
+
export declare function sort<T>(data: T, ascending?: boolean, sortPrimitiveArrays?: boolean): T;
|
|
12
|
+
//#endregion
|
|
13
|
+
export type { NonSortableType, ObjectType, SortOptions, SortedEntry };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type ObjectType = Record<string | symbol, unknown>;
|
|
3
|
+
type SortedEntry = [string | symbol, unknown];
|
|
4
|
+
type NonSortableType = Date | RegExp | (() => unknown) | ((...args: unknown[]) => unknown) | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown>;
|
|
5
|
+
type SortOptions = {
|
|
6
|
+
ascending: boolean;
|
|
7
|
+
sortPrimitiveArrays: boolean;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/index.d.ts
|
|
11
|
+
export declare function sort<T>(data: T, ascending?: boolean, sortPrimitiveArrays?: boolean): T;
|
|
12
|
+
//#endregion
|
|
13
|
+
export type { NonSortableType, ObjectType, SortOptions, SortedEntry };
|
package/dist/index.js
CHANGED
|
@@ -1,181 +1,126 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
sort: () => sort
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
|
|
27
|
-
// src/types.ts
|
|
28
|
-
var NULLISH_VALUES = [null, void 0];
|
|
29
|
-
var SORTABLE_PRIMITIVE_TYPES = [
|
|
30
|
-
"string",
|
|
31
|
-
"number",
|
|
32
|
-
"boolean"
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/types.ts
|
|
3
|
+
const NULLISH_VALUES = [null, void 0];
|
|
4
|
+
const SORTABLE_PRIMITIVE_TYPES = [
|
|
5
|
+
"string",
|
|
6
|
+
"number",
|
|
7
|
+
"boolean"
|
|
33
8
|
];
|
|
34
|
-
|
|
35
|
-
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/type-guards.ts
|
|
36
11
|
function isNullish(value) {
|
|
37
|
-
|
|
12
|
+
return NULLISH_VALUES.includes(value);
|
|
38
13
|
}
|
|
39
14
|
function hasNullishValues(array) {
|
|
40
|
-
|
|
15
|
+
return array.some(isNullish);
|
|
41
16
|
}
|
|
42
17
|
function isSortablePrimitive(value) {
|
|
43
|
-
|
|
44
|
-
typeof value
|
|
45
|
-
);
|
|
18
|
+
return SORTABLE_PRIMITIVE_TYPES.includes(typeof value);
|
|
46
19
|
}
|
|
47
20
|
function isPrimitive(data) {
|
|
48
|
-
|
|
21
|
+
return isSortablePrimitive(data) || isNullish(data);
|
|
49
22
|
}
|
|
50
23
|
function isObject(data) {
|
|
51
|
-
|
|
24
|
+
return typeof data === "object" && data !== null;
|
|
52
25
|
}
|
|
53
26
|
function isNonSortableObject(obj) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// src/comparators.ts
|
|
27
|
+
return [
|
|
28
|
+
Date,
|
|
29
|
+
RegExp,
|
|
30
|
+
Function,
|
|
31
|
+
Error,
|
|
32
|
+
Map,
|
|
33
|
+
Set,
|
|
34
|
+
WeakMap,
|
|
35
|
+
WeakSet,
|
|
36
|
+
Promise
|
|
37
|
+
].some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/comparators.ts
|
|
69
41
|
function compareSortablePrimitives(a, b, ascending) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return compareNumbers(a, b, ascending);
|
|
75
|
-
}
|
|
76
|
-
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
77
|
-
return compareBooleans(a, b, ascending);
|
|
78
|
-
}
|
|
79
|
-
return 0;
|
|
42
|
+
if (typeof a === "string" && typeof b === "string") return ascending ? a.localeCompare(b) : b.localeCompare(a);
|
|
43
|
+
if (typeof a === "number" && typeof b === "number") return compareNumbers(a, b, ascending);
|
|
44
|
+
if (typeof a === "boolean" && typeof b === "boolean") return compareBooleans(a, b, ascending);
|
|
45
|
+
return 0;
|
|
80
46
|
}
|
|
81
47
|
function compareNumbers(a, b, ascending) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
48
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return 0;
|
|
49
|
+
if (Number.isNaN(a)) return 1;
|
|
50
|
+
if (Number.isNaN(b)) return -1;
|
|
51
|
+
return ascending ? a - b : b - a;
|
|
86
52
|
}
|
|
87
53
|
function compareBooleans(a, b, ascending) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
54
|
+
if (a === b) return 0;
|
|
55
|
+
if (a) return ascending ? 1 : -1;
|
|
56
|
+
return ascending ? -1 : 1;
|
|
91
57
|
}
|
|
92
58
|
function compareObjectKeys(keyA, keyB, ascending) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
59
|
+
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
60
|
+
if (typeof keyA === "symbol") return 1;
|
|
61
|
+
if (typeof keyB === "symbol") return -1;
|
|
62
|
+
const stringA = keyA;
|
|
63
|
+
const stringB = keyB;
|
|
64
|
+
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/array-sorter.ts
|
|
102
68
|
function sortArray(array, options) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
return array.map((item) => sortRecursively(item, options));
|
|
69
|
+
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) return sortPrimitiveArray(array, options.ascending);
|
|
70
|
+
return array.map((item) => sortRecursively(item, options));
|
|
107
71
|
}
|
|
108
72
|
function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
|
|
109
|
-
|
|
73
|
+
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
110
74
|
}
|
|
111
75
|
function canSortPrimitiveArray(array) {
|
|
112
|
-
|
|
76
|
+
return allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array);
|
|
113
77
|
}
|
|
114
78
|
function allItemsAreSortablePrimitives(array) {
|
|
115
|
-
|
|
79
|
+
return array.every(isSortablePrimitive);
|
|
116
80
|
}
|
|
117
81
|
function sortPrimitiveArray(array, ascending) {
|
|
118
|
-
|
|
82
|
+
return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
|
|
119
83
|
}
|
|
120
84
|
function allItemsHaveSameSortableType(array) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return array.every(isSortablePrimitive);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/object-sorter.ts
|
|
85
|
+
if (array.length === 0) return true;
|
|
86
|
+
if (hasNullishValues(array)) return false;
|
|
87
|
+
const firstItem = array[0];
|
|
88
|
+
if (!isSortablePrimitive(firstItem)) return false;
|
|
89
|
+
const expectedType = typeof firstItem;
|
|
90
|
+
return array.every((item) => typeof item === expectedType);
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/object-sorter.ts
|
|
134
94
|
function sortObject(obj, options) {
|
|
135
|
-
|
|
136
|
-
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
137
|
-
return createSortedObject(sortedEntries, options);
|
|
95
|
+
return createSortedObject(sortObjectEntries(collectObjectEntries(obj), options.ascending), options);
|
|
138
96
|
}
|
|
139
97
|
function collectObjectEntries(obj) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
return [...stringEntries, ...symbolEntries];
|
|
98
|
+
const stringEntries = Object.entries(obj);
|
|
99
|
+
const symbolEntries = Object.getOwnPropertySymbols(obj).map((symbol) => [symbol, obj[symbol]]);
|
|
100
|
+
return [...stringEntries, ...symbolEntries];
|
|
145
101
|
}
|
|
146
102
|
function sortObjectEntries(entries, ascending) {
|
|
147
|
-
|
|
148
|
-
([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
|
|
149
|
-
);
|
|
103
|
+
return entries.sort(([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending));
|
|
150
104
|
}
|
|
151
105
|
function createSortedObject(entries, options) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// src/core.ts
|
|
106
|
+
const sortedEntries = entries.map(([key, value]) => [key, sortRecursively(value, options)]);
|
|
107
|
+
return Object.fromEntries(sortedEntries);
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/core.ts
|
|
160
111
|
function sortRecursively(data, options) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return sortObject(data, options);
|
|
169
|
-
}
|
|
170
|
-
return data;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// src/index.ts
|
|
112
|
+
if (Array.isArray(data)) return sortArray(data, options);
|
|
113
|
+
if (isPrimitive(data) || isNonSortableObject(data)) return data;
|
|
114
|
+
if (isObject(data)) return sortObject(data, options);
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/index.ts
|
|
174
119
|
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
175
|
-
|
|
176
|
-
|
|
120
|
+
return sortRecursively(data, {
|
|
121
|
+
ascending,
|
|
122
|
+
sortPrimitiveArrays
|
|
123
|
+
});
|
|
177
124
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
sort
|
|
181
|
-
});
|
|
125
|
+
//#endregion
|
|
126
|
+
exports.sort = sort;
|
package/dist/index.mjs
CHANGED
|
@@ -1,154 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
const NULLISH_VALUES = [null, void 0];
|
|
3
|
+
const SORTABLE_PRIMITIVE_TYPES = [
|
|
4
|
+
"string",
|
|
5
|
+
"number",
|
|
6
|
+
"boolean"
|
|
7
7
|
];
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/type-guards.ts
|
|
10
10
|
function isNullish(value) {
|
|
11
|
-
|
|
11
|
+
return NULLISH_VALUES.includes(value);
|
|
12
12
|
}
|
|
13
13
|
function hasNullishValues(array) {
|
|
14
|
-
|
|
14
|
+
return array.some(isNullish);
|
|
15
15
|
}
|
|
16
16
|
function isSortablePrimitive(value) {
|
|
17
|
-
|
|
18
|
-
typeof value
|
|
19
|
-
);
|
|
17
|
+
return SORTABLE_PRIMITIVE_TYPES.includes(typeof value);
|
|
20
18
|
}
|
|
21
19
|
function isPrimitive(data) {
|
|
22
|
-
|
|
20
|
+
return isSortablePrimitive(data) || isNullish(data);
|
|
23
21
|
}
|
|
24
22
|
function isObject(data) {
|
|
25
|
-
|
|
23
|
+
return typeof data === "object" && data !== null;
|
|
26
24
|
}
|
|
27
25
|
function isNonSortableObject(obj) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// src/comparators.ts
|
|
26
|
+
return [
|
|
27
|
+
Date,
|
|
28
|
+
RegExp,
|
|
29
|
+
Function,
|
|
30
|
+
Error,
|
|
31
|
+
Map,
|
|
32
|
+
Set,
|
|
33
|
+
WeakMap,
|
|
34
|
+
WeakSet,
|
|
35
|
+
Promise
|
|
36
|
+
].some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/comparators.ts
|
|
43
40
|
function compareSortablePrimitives(a, b, ascending) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return compareNumbers(a, b, ascending);
|
|
49
|
-
}
|
|
50
|
-
if (typeof a === "boolean" && typeof b === "boolean") {
|
|
51
|
-
return compareBooleans(a, b, ascending);
|
|
52
|
-
}
|
|
53
|
-
return 0;
|
|
41
|
+
if (typeof a === "string" && typeof b === "string") return ascending ? a.localeCompare(b) : b.localeCompare(a);
|
|
42
|
+
if (typeof a === "number" && typeof b === "number") return compareNumbers(a, b, ascending);
|
|
43
|
+
if (typeof a === "boolean" && typeof b === "boolean") return compareBooleans(a, b, ascending);
|
|
44
|
+
return 0;
|
|
54
45
|
}
|
|
55
46
|
function compareNumbers(a, b, ascending) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return 0;
|
|
48
|
+
if (Number.isNaN(a)) return 1;
|
|
49
|
+
if (Number.isNaN(b)) return -1;
|
|
50
|
+
return ascending ? a - b : b - a;
|
|
60
51
|
}
|
|
61
52
|
function compareBooleans(a, b, ascending) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
if (a === b) return 0;
|
|
54
|
+
if (a) return ascending ? 1 : -1;
|
|
55
|
+
return ascending ? -1 : 1;
|
|
65
56
|
}
|
|
66
57
|
function compareObjectKeys(keyA, keyB, ascending) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
|
|
59
|
+
if (typeof keyA === "symbol") return 1;
|
|
60
|
+
if (typeof keyB === "symbol") return -1;
|
|
61
|
+
const stringA = keyA;
|
|
62
|
+
const stringB = keyB;
|
|
63
|
+
return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/array-sorter.ts
|
|
76
67
|
function sortArray(array, options) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
return array.map((item) => sortRecursively(item, options));
|
|
68
|
+
if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) return sortPrimitiveArray(array, options.ascending);
|
|
69
|
+
return array.map((item) => sortRecursively(item, options));
|
|
81
70
|
}
|
|
82
71
|
function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
|
|
83
|
-
|
|
72
|
+
return sortPrimitiveArrays && canSortPrimitiveArray(array);
|
|
84
73
|
}
|
|
85
74
|
function canSortPrimitiveArray(array) {
|
|
86
|
-
|
|
75
|
+
return allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array);
|
|
87
76
|
}
|
|
88
77
|
function allItemsAreSortablePrimitives(array) {
|
|
89
|
-
|
|
78
|
+
return array.every(isSortablePrimitive);
|
|
90
79
|
}
|
|
91
80
|
function sortPrimitiveArray(array, ascending) {
|
|
92
|
-
|
|
81
|
+
return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
|
|
93
82
|
}
|
|
94
83
|
function allItemsHaveSameSortableType(array) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return array.every(isSortablePrimitive);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/object-sorter.ts
|
|
84
|
+
if (array.length === 0) return true;
|
|
85
|
+
if (hasNullishValues(array)) return false;
|
|
86
|
+
const firstItem = array[0];
|
|
87
|
+
if (!isSortablePrimitive(firstItem)) return false;
|
|
88
|
+
const expectedType = typeof firstItem;
|
|
89
|
+
return array.every((item) => typeof item === expectedType);
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/object-sorter.ts
|
|
108
93
|
function sortObject(obj, options) {
|
|
109
|
-
|
|
110
|
-
const sortedEntries = sortObjectEntries(entries, options.ascending);
|
|
111
|
-
return createSortedObject(sortedEntries, options);
|
|
94
|
+
return createSortedObject(sortObjectEntries(collectObjectEntries(obj), options.ascending), options);
|
|
112
95
|
}
|
|
113
96
|
function collectObjectEntries(obj) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
118
|
-
return [...stringEntries, ...symbolEntries];
|
|
97
|
+
const stringEntries = Object.entries(obj);
|
|
98
|
+
const symbolEntries = Object.getOwnPropertySymbols(obj).map((symbol) => [symbol, obj[symbol]]);
|
|
99
|
+
return [...stringEntries, ...symbolEntries];
|
|
119
100
|
}
|
|
120
101
|
function sortObjectEntries(entries, ascending) {
|
|
121
|
-
|
|
122
|
-
([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
|
|
123
|
-
);
|
|
102
|
+
return entries.sort(([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending));
|
|
124
103
|
}
|
|
125
104
|
function createSortedObject(entries, options) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// src/core.ts
|
|
105
|
+
const sortedEntries = entries.map(([key, value]) => [key, sortRecursively(value, options)]);
|
|
106
|
+
return Object.fromEntries(sortedEntries);
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/core.ts
|
|
134
110
|
function sortRecursively(data, options) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return sortObject(data, options);
|
|
143
|
-
}
|
|
144
|
-
return data;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// src/index.ts
|
|
111
|
+
if (Array.isArray(data)) return sortArray(data, options);
|
|
112
|
+
if (isPrimitive(data) || isNonSortableObject(data)) return data;
|
|
113
|
+
if (isObject(data)) return sortObject(data, options);
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/index.ts
|
|
148
118
|
function sort(data, ascending = true, sortPrimitiveArrays = false) {
|
|
149
|
-
|
|
150
|
-
|
|
119
|
+
return sortRecursively(data, {
|
|
120
|
+
ascending,
|
|
121
|
+
sortPrimitiveArrays
|
|
122
|
+
});
|
|
151
123
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
124
|
+
//#endregion
|
|
125
|
+
export { sort };
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamtamchik/json-deep-sort",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.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",
|
|
7
|
-
"types": "./
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"author": "Yuri Tkachenko <yuri.tam.tkachenko@gmail.com>",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"homepage": "https://github.com/tamtamchik/json-deep-sort#readme",
|
|
@@ -32,35 +32,43 @@
|
|
|
32
32
|
"json-tools"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@types/node": "^
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"eslint-config-prettier": "^10.1.8",
|
|
42
|
-
"eslint-plugin-prettier": "^5.5.4",
|
|
43
|
-
"jiti": "^2.5.1",
|
|
44
|
-
"prettier": "^3.6.2",
|
|
45
|
-
"tsup": "^8.3.0",
|
|
46
|
-
"tsx": "^4.21.0",
|
|
47
|
-
"typescript": "^6.0.2"
|
|
35
|
+
"@biomejs/biome": "2.5.15",
|
|
36
|
+
"@types/node": "^26.6.4",
|
|
37
|
+
"c8": "^12.0.0",
|
|
38
|
+
"tsdown": "^0.23.0",
|
|
39
|
+
"tsx": "^4.23.15",
|
|
40
|
+
"typescript": "~7.0.2"
|
|
48
41
|
},
|
|
49
42
|
"exports": {
|
|
50
43
|
".": {
|
|
51
|
-
"types": "./
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
52
45
|
"require": "./dist/index.js",
|
|
53
46
|
"import": "./dist/index.mjs"
|
|
54
47
|
}
|
|
55
48
|
},
|
|
56
49
|
"scripts": {
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
50
|
+
"check": "biome check .",
|
|
51
|
+
"fix": "biome check --write .",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"build": "tsdown",
|
|
54
|
+
"dev": "tsdown --watch src",
|
|
55
|
+
"prepublishOnly": "npm run check && npm run typecheck && npm run build && npm test && npm run test:package",
|
|
60
56
|
"test": "node --import tsx --test test/*.test.ts",
|
|
61
57
|
"coverage": "c8 --reporter=text --reporter=lcov node --import tsx --test test/*.test.ts",
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
"test:package": "node test/package.test.mjs"
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"dist"
|
|
62
|
+
],
|
|
63
|
+
"devEngines": {
|
|
64
|
+
"runtime": {
|
|
65
|
+
"name": "node",
|
|
66
|
+
"version": "^22.18.0 || ^24.11.0 || ^26.0.0",
|
|
67
|
+
"onFail": "error"
|
|
68
|
+
},
|
|
69
|
+
"packageManager": {
|
|
70
|
+
"name": "npm",
|
|
71
|
+
"onFail": "error"
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
}
|
package/src/array-sorter.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { 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';
|
package/src/comparators.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { 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
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { SortOptions } from './types';
|
|
2
|
-
import { sortRecursively } from './core';
|
|
3
|
-
|
|
4
|
-
export function sort<T>(
|
|
5
|
-
data: T,
|
|
6
|
-
ascending = true,
|
|
7
|
-
sortPrimitiveArrays = false
|
|
8
|
-
): T {
|
|
9
|
-
const options: SortOptions = { ascending, sortPrimitiveArrays };
|
|
10
|
-
return sortRecursively(data, options);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Re-export types for convenience
|
|
14
|
-
export type {
|
|
15
|
-
SortOptions,
|
|
16
|
-
ObjectType,
|
|
17
|
-
SortedEntry,
|
|
18
|
-
NonSortableType,
|
|
19
|
-
} from './types';
|
package/src/object-sorter.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { 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';
|
package/src/type-guards.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { NULLISH_VALUES, SORTABLE_PRIMITIVE_TYPES } from './types';
|
|
2
|
-
import type { NullishValue, NonSortableType, ObjectType } from './types';
|
|
3
|
-
|
|
4
|
-
// Type guards - single responsibility, clear naming
|
|
5
|
-
export function isNullish(value: unknown): value is NullishValue {
|
|
6
|
-
return NULLISH_VALUES.includes(value as NullishValue);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function hasNullishValues(array: unknown[]): boolean {
|
|
10
|
-
return array.some(isNullish);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function isSortablePrimitive(
|
|
14
|
-
value: unknown
|
|
15
|
-
): value is string | number | boolean {
|
|
16
|
-
return SORTABLE_PRIMITIVE_TYPES.includes(
|
|
17
|
-
typeof value as 'string' | 'number' | 'boolean'
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function isPrimitive(data: unknown): boolean {
|
|
22
|
-
return isSortablePrimitive(data) || isNullish(data);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isObject(data: unknown): data is ObjectType {
|
|
26
|
-
return typeof data === 'object' && data !== null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function isNonSortableObject(obj: unknown): obj is NonSortableType {
|
|
30
|
-
const nonSortableTypes = [
|
|
31
|
-
Date,
|
|
32
|
-
RegExp,
|
|
33
|
-
Function,
|
|
34
|
-
Error,
|
|
35
|
-
Map,
|
|
36
|
-
Set,
|
|
37
|
-
WeakMap,
|
|
38
|
-
WeakSet,
|
|
39
|
-
Promise,
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
nonSortableTypes.some((type) => obj instanceof type) ||
|
|
44
|
-
Symbol.iterator in Object(obj)
|
|
45
|
-
);
|
|
46
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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
|
-
};
|