@okutils/array-to-tree 0.0.1-alpha.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/LICENSE +22 -0
- package/README.md +157 -0
- package/README.zh-CN.md +157 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016-2022 Philipp Alferov.
|
|
4
|
+
Copyright (c) 2025-present Luke Na.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @oktils/array-to-tree
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@oktils/array-to-tree)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
A TypeScript utility library for converting a flat array structure into a hierarchical tree structure based on parent-child relationships.
|
|
7
|
+
|
|
8
|
+
This project is based on [alferov/array-to-tree](https://github.com/alferov/array-to-tree), with modernizations, feature enhancements, and bug fixes applied to the original code.
|
|
9
|
+
|
|
10
|
+
> [!IMPORTANT]
|
|
11
|
+
>
|
|
12
|
+
> Currently in Alpha stage. Bugs or breaking changes may occur.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Modern**: Written in modern TypeScript and ES for better type support.
|
|
17
|
+
- **Functional**: Adopts a functional programming paradigm, does not mutate the original data, and avoids side effects.
|
|
18
|
+
- **Configurable**: Allows customization of field names and other behaviors to adapt to different data structures.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Using pnpm
|
|
24
|
+
pnpm add @oktils/array-to-tree
|
|
25
|
+
|
|
26
|
+
# Using yarn
|
|
27
|
+
yarn add @oktils/array-to-tree
|
|
28
|
+
|
|
29
|
+
# Using npm
|
|
30
|
+
npm install @oktils/array-to-tree
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { arrayToTree } from "@oktils/array-to-tree";
|
|
39
|
+
|
|
40
|
+
const data = [
|
|
41
|
+
{ id: 1, name: "A", parentId: null },
|
|
42
|
+
{ id: 2, name: "B", parentId: 1 },
|
|
43
|
+
{ id: 3, name: "C", parentId: 1 },
|
|
44
|
+
{ id: 4, name: "D", parentId: 2 },
|
|
45
|
+
{ id: 5, name: "E", parentId: null },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const tree = arrayToTree(data);
|
|
49
|
+
|
|
50
|
+
console.log(tree);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Output:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
[
|
|
57
|
+
{
|
|
58
|
+
"id": 1,
|
|
59
|
+
"name": "A",
|
|
60
|
+
"parentId": null,
|
|
61
|
+
"children": [
|
|
62
|
+
{
|
|
63
|
+
"id": 2,
|
|
64
|
+
"name": "B",
|
|
65
|
+
"parentId": 1,
|
|
66
|
+
"children": [
|
|
67
|
+
{
|
|
68
|
+
"id": 4,
|
|
69
|
+
"name": "D",
|
|
70
|
+
"parentId": 2
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": 3,
|
|
76
|
+
"name": "C",
|
|
77
|
+
"parentId": 1
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": 5,
|
|
83
|
+
"name": "E",
|
|
84
|
+
"parentId": null
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### API
|
|
90
|
+
|
|
91
|
+
`arrayToTree(data, options?)`
|
|
92
|
+
|
|
93
|
+
- `data`: `Record<string, any>[]` - A flat array containing the data.
|
|
94
|
+
- `options`: `ArrayToTreeOptions` (optional) - A configuration options object.
|
|
95
|
+
|
|
96
|
+
### Configuration Options
|
|
97
|
+
|
|
98
|
+
You can pass an `options` object to customize field names and other behaviors.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// src/types/options.ts
|
|
102
|
+
export interface ArrayToTreeOptions {
|
|
103
|
+
childrenId?: string;
|
|
104
|
+
customId?: string;
|
|
105
|
+
parentId?: string;
|
|
106
|
+
rootId?: string;
|
|
107
|
+
allowSelfAsParent?: boolean;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | Description |
|
|
112
|
+
| ------------------- | --------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| `customId` | `string` | `'id'` | Specifies the field name for the unique identifier of a node. |
|
|
114
|
+
| `parentId` | `string` | `'parentId'` | Specifies the field name for associating with the parent node. |
|
|
115
|
+
| `childrenId` | `string` | `'children'` | Specifies the field name for the array of child nodes in the generated tree. |
|
|
116
|
+
| `allowSelfAsParent` | `boolean` | `false` | Whether to allow a node's `parentId` to be equal to its own `id`. |
|
|
117
|
+
| `rootId` | `string` | `'__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__'` | An internal virtual root ID, which usually does not need to be changed. The `parentId` of root nodes should be `null` or `undefined`. |
|
|
118
|
+
|
|
119
|
+
#### Example: Using Custom Fields
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { arrayToTree } from "@oktils/array-to-tree";
|
|
123
|
+
|
|
124
|
+
const data = [
|
|
125
|
+
{ key: "node-1", parent: null, title: "Node 1" },
|
|
126
|
+
{ key: "node-2", parent: "node-1", title: "Node 2" },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const tree = arrayToTree(data, {
|
|
130
|
+
customId: "key",
|
|
131
|
+
parentId: "parent",
|
|
132
|
+
childrenId: "nodes",
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
##### Output
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
"key": "node-1",
|
|
142
|
+
"parent": null,
|
|
143
|
+
"title": "Node 1",
|
|
144
|
+
"nodes": [
|
|
145
|
+
{
|
|
146
|
+
"key": "node-2",
|
|
147
|
+
"parent": "node-1",
|
|
148
|
+
"title": "Node 2"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 📄 License
|
|
156
|
+
|
|
157
|
+
[MIT](LICENSE) © Luke Na
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @oktils/array-to-tree
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@oktils/array-to-tree)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
一个 TypeScript 工具库,用于将扁平的数组结构通过父子关系转换为层级树结构。
|
|
7
|
+
|
|
8
|
+
这个项目基于 [alferov/array-to-tree](https://github.com/alferov/array-to-tree),并针对原始代码做了现代化改造、功能增强和一些错误修复。
|
|
9
|
+
|
|
10
|
+
> [!IMPORTANT]
|
|
11
|
+
>
|
|
12
|
+
> 目前处于 Alpha 阶段,可能会存在 bug 或不兼容的变更。
|
|
13
|
+
|
|
14
|
+
## 特性
|
|
15
|
+
|
|
16
|
+
- **现代化**:使用现代 TypeScript 和 ES 编写,提供更好的类型支持。
|
|
17
|
+
- **函数式编程**:采用函数式编程范式,不修改原始数据,避免副作用。
|
|
18
|
+
- **可配置**:允许自定义字段名和其他行为,适应不同的数据结构。
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 使用 pnpm
|
|
24
|
+
pnpm add @oktils/array-to-tree
|
|
25
|
+
|
|
26
|
+
# 使用 yarn
|
|
27
|
+
yarn add @oktils/array-to-tree
|
|
28
|
+
|
|
29
|
+
# 使用 npm
|
|
30
|
+
npm install @oktils/array-to-tree
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 使用方法
|
|
34
|
+
|
|
35
|
+
### 基本用法
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { arrayToTree } from "@oktils/array-to-tree";
|
|
39
|
+
|
|
40
|
+
const data = [
|
|
41
|
+
{ id: 1, name: "A", parentId: null },
|
|
42
|
+
{ id: 2, name: "B", parentId: 1 },
|
|
43
|
+
{ id: 3, name: "C", parentId: 1 },
|
|
44
|
+
{ id: 4, name: "D", parentId: 2 },
|
|
45
|
+
{ id: 5, name: "E", parentId: null },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const tree = arrayToTree(data);
|
|
49
|
+
|
|
50
|
+
console.log(tree);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
##### 输出结果
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
[
|
|
57
|
+
{
|
|
58
|
+
"id": 1,
|
|
59
|
+
"name": "A",
|
|
60
|
+
"parentId": null,
|
|
61
|
+
"children": [
|
|
62
|
+
{
|
|
63
|
+
"id": 2,
|
|
64
|
+
"name": "B",
|
|
65
|
+
"parentId": 1,
|
|
66
|
+
"children": [
|
|
67
|
+
{
|
|
68
|
+
"id": 4,
|
|
69
|
+
"name": "D",
|
|
70
|
+
"parentId": 2
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": 3,
|
|
76
|
+
"name": "C",
|
|
77
|
+
"parentId": 1
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": 5,
|
|
83
|
+
"name": "E",
|
|
84
|
+
"parentId": null
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### API
|
|
90
|
+
|
|
91
|
+
`arrayToTree(data, options?)`
|
|
92
|
+
|
|
93
|
+
- `data`: `Record<string, any>[]` - 包含数据的扁平数组。
|
|
94
|
+
- `options`: `ArrayToTreeOptions` (可选) - 配置选项对象。
|
|
95
|
+
|
|
96
|
+
### 配置选项
|
|
97
|
+
|
|
98
|
+
您可以传入一个 `options` 对象来自定义字段名和其他行为。
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// src/types/options.ts
|
|
102
|
+
export interface ArrayToTreeOptions {
|
|
103
|
+
childrenId?: string;
|
|
104
|
+
customId?: string;
|
|
105
|
+
parentId?: string;
|
|
106
|
+
rootId?: string;
|
|
107
|
+
allowSelfAsParent?: boolean;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| 选项 | 类型 | 默认值 | 描述 |
|
|
112
|
+
| ------------------- | --------- | ------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
113
|
+
| `customId` | `string` | `'id'` | 指定节点唯一标识的字段名。 |
|
|
114
|
+
| `parentId` | `string` | `'parentId'` | 指定关联父节点的字段名。 |
|
|
115
|
+
| `childrenId` | `string` | `'children'` | 指定生成的树中用于存放子节点数组的字段名。 |
|
|
116
|
+
| `allowSelfAsParent` | `boolean` | `false` | 是否允许一个节点的 `parentId` 等于它自身的 `id`。 |
|
|
117
|
+
| `rootId` | `string` | `'__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__'` | 内部使用的虚拟根 ID,通常不需要修改。根节点的 `parentId` 应为 `null` 或 `undefined`。 |
|
|
118
|
+
|
|
119
|
+
#### 示例:使用自定义字段
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { arrayToTree } from "@oktils/array-to-tree";
|
|
123
|
+
|
|
124
|
+
const data = [
|
|
125
|
+
{ key: "node-1", parent: null, title: "Node 1" },
|
|
126
|
+
{ key: "node-2", parent: "node-1", title: "Node 2" },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const tree = arrayToTree(data, {
|
|
130
|
+
customId: "key",
|
|
131
|
+
parentId: "parent",
|
|
132
|
+
childrenId: "nodes",
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
输出结果:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
"key": "node-1",
|
|
142
|
+
"parent": null,
|
|
143
|
+
"title": "Node 1",
|
|
144
|
+
"nodes": [
|
|
145
|
+
{
|
|
146
|
+
"key": "node-2",
|
|
147
|
+
"parent": "node-1",
|
|
148
|
+
"title": "Node 2"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 📄 许可证
|
|
156
|
+
|
|
157
|
+
[MIT](LICENSE) © Luke Na
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";class r extends Error{constructor(r){super(r),this.name="ArrayToTreeError"}}const t=(r,e,n,o)=>Array.isArray(e)?e.map(e=>{const s={...e},a=r[e[n]];return a&&(s[o]=t(r,a,n,o)),s}):[],e=(r,t)=>{const e=t.split(/[.[\]]/g);let n=r;for(const r of e){if(null===n)return null;if(void 0===n)return null;const t=r.replace(/['"]/g,"");""!==t.trim()&&(n=n[t])}return void 0===n?null:n};exports.ArrayToTreeError=r,exports.arrayToTree=(n,o)=>{const s=(r=>({allowSelfAsParent:(r=r??{}).allowSelfAsParent??!1,childrenId:r.childrenId??"children",customId:r.customId??"id",parentId:r.parentId??"parentId",rootId:r.rootId??"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__"}))(o);if(!Array.isArray(n))throw new r("Expected an array but got an invalid argument.");const a=((t,n)=>{const o={};for(const e of t){const t=e[n.customId];if(null!=t&&Object.hasOwn(o,t))throw new r(`Duplicate node id "${t}" detected.`);o[t]=e}return t.reduce((t,s)=>{const a=e(s,n.customId);let c=e(s,n.parentId);if(c===a){if(!n.allowSelfAsParent)throw new r(`Node "${a}" cannot be its own parent (self reference found).`);c=n.rootId}return c&&Object.hasOwn(o,c)||(c=n.rootId),c&&Object.hasOwn(t,c)?(t[c].push(s),t):(t[c]=[s],t)},{})})(n,s);return t(a,a[s.rootId],s.customId,s.childrenId)};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/create-tree.ts","../../../../src/internal/get.ts","../../../../src/utils/array-to-tree.ts","../../../../src/internal/resolve-options.ts","../../../../src/internal/group-by-parents.ts"],"sourcesContent":["export class ArrayToTreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ArrayToTreeError\";\n }\n}\n","export const createTree = (\n grouped: Record<string, any[]>,\n rootNodes: any[],\n customId: string,\n childrenProperty: string,\n) => {\n if (!Array.isArray(rootNodes)) {\n return [];\n }\n\n return rootNodes.map((node) => {\n const newNode = { ...node };\n const children = grouped[node[customId]];\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\n};\n","/**\n * This code is modified from the radash project.\n * @see https://github.com/sodiray/radash\n */\nexport const get = (value: any, path: string) => {\n const segments = path.split(/[.[\\]]/g);\n let current: any = value;\n for (const key of segments) {\n if (current === null) return null;\n if (current === undefined) return null;\n const dequoted = key.replace(/['\"]/g, \"\");\n if (dequoted.trim() === \"\") continue;\n current = current[dequoted];\n }\n if (current === undefined) return null;\n return current;\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport { createTree, groupByParents, resolveOptions } from \"../internal\";\nimport type {\n ArrayToTreeOptions,\n NormalizedArrayToTreeOptions,\n} from \"../types\";\n\nexport const arrayToTree = (\n data: Record<string, any>[],\n options?: ArrayToTreeOptions,\n) => {\n const normalizeOptions = resolveOptions(\n options,\n ) as NormalizedArrayToTreeOptions;\n\n if (!Array.isArray(data)) {\n throw new ArrayToTreeError(\n \"Expected an array but got an invalid argument.\",\n );\n }\n\n const grouped = groupByParents(data, normalizeOptions);\n\n return createTree(\n grouped,\n grouped[normalizeOptions.rootId],\n normalizeOptions.customId,\n normalizeOptions.childrenId,\n );\n};\n","import type { ArrayToTreeOptions } from \"../types\";\n\nexport const resolveOptions = (\n options?: ArrayToTreeOptions,\n): Required<ArrayToTreeOptions> => {\n options = options ?? {};\n return {\n allowSelfAsParent: options.allowSelfAsParent ?? false,\n childrenId: options.childrenId ?? \"children\",\n customId: options.customId ?? \"id\",\n parentId: options.parentId ?? \"parentId\",\n rootId: options.rootId ?? \"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__\",\n };\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport type { NormalizedArrayToTreeOptions } from \"../types\";\nimport { get } from \"./get\";\n\nexport const groupByParents = (\n array: Record<string, any>[],\n options: NormalizedArrayToTreeOptions,\n) => {\n const arrayById: Record<string, any> = {};\n for (const item of array) {\n const key = item[options.customId];\n if (key != null && Object.hasOwn(arrayById, key)) {\n throw new ArrayToTreeError(`Duplicate node id \"${key}\" detected.`);\n }\n arrayById[key] = item;\n }\n\n return array.reduce((prev, item) => {\n const id = get(item, options.customId);\n let parentId = get(item, options.parentId);\n\n // Handle self-referencing nodes\n if (parentId === id) {\n if (!options.allowSelfAsParent) {\n throw new ArrayToTreeError(\n `Node \"${id}\" cannot be its own parent (self reference found).`,\n );\n }\n parentId = options.rootId;\n }\n\n if (!parentId || !Object.hasOwn(arrayById, parentId)) {\n parentId = options.rootId;\n }\n\n if (parentId && Object.hasOwn(prev, parentId)) {\n prev[parentId].push(item);\n return prev;\n }\n\n prev[parentId] = [item];\n return prev;\n }, {});\n};\n"],"names":["ArrayToTreeError","Error","constructor","message","super","this","name","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","children","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","data","options","normalizeOptions","allowSelfAsParent","childrenId","parentId","rootId","resolveOptions","array","arrayById","item","Object","hasOwn","reduce","prev","id","push","groupByParents"],"mappings":"aAAM,MAAOA,UAAyBC,MACpC,WAAAC,CAAYC,GACVC,MAAMD,GACNE,KAAKC,KAAO,kBACd,ECJK,MAAMC,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAAWT,EAAQO,EAAKL,IAS9B,OARIO,IACFD,EAAQL,GAAoBJ,EAC1BC,EACAS,EACAP,EACAC,IAGGK,IAdA,GCHEE,EAAM,CAACC,EAAYC,KAC9B,MAAMC,EAAWD,EAAKE,MAAM,WAC5B,IAAIC,EAAeJ,EACnB,IAAK,MAAMK,KAAOH,EAAU,CAC1B,GAAgB,OAAZE,EAAkB,OAAO,KAC7B,QAAgBE,IAAZF,EAAuB,OAAO,KAClC,MAAMG,EAAWF,EAAIG,QAAQ,QAAS,IACd,KAApBD,EAASE,SACbL,EAAUA,EAAQG,GACpB,CACA,YAAgBD,IAAZF,EAA8B,KAC3BA,kDCRkB,CACzBM,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCvB,SAAUoB,EAAQpB,UAAY,KAC9BwB,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKlB,MAAMC,QAAQgB,GACjB,MAAM,IAAI7B,EACR,kDAIJ,MAAMQ,EEjBsB,EAC5B6B,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EACvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMb,EAAMe,EAAKT,EAAQpB,UACzB,GAAW,MAAPc,GAAegB,OAAOC,OAAOH,EAAWd,GAC1C,MAAM,IAAIxB,EAAiB,sBAAsBwB,gBAEnDc,EAAUd,GAAOe,CACnB,CAEA,OAAOF,EAAMK,OAAO,CAACC,EAAMJ,KACzB,MAAMK,EAAK1B,EAAIqB,EAAMT,EAAQpB,UAC7B,IAAIwB,EAAWhB,EAAIqB,EAAMT,EAAQI,UAGjC,GAAIA,IAAaU,EAAI,CACnB,IAAKd,EAAQE,kBACX,MAAM,IAAIhC,EACR,SAAS4C,uDAGbV,EAAWJ,EAAQK,MACrB,CAMA,OAJKD,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBD,GAAYM,OAAOC,OAAOE,EAAMT,IAClCS,EAAKT,GAAUW,KAAKN,GACbI,IAGTA,EAAKT,GAAY,CAACK,GACXI,IACN,CAAA,IFrBaG,CAAejB,EAAME,GAErC,OAAOxB,EACLC,EACAA,EAAQuB,EAAiBI,QACzBJ,EAAiBrB,SACjBqB,EAAiBE"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
class r extends Error{constructor(r){super(r),this.name="ArrayToTreeError"}}const t=(r,e,n,o)=>Array.isArray(e)?e.map(e=>{const d={...e},s=r[e[n]];return s&&(d[o]=t(r,s,n,o)),d}):[],e=(r,t)=>{const e=t.split(/[.[\]]/g);let n=r;for(const r of e){if(null===n)return null;if(void 0===n)return null;const t=r.replace(/['"]/g,"");""!==t.trim()&&(n=n[t])}return void 0===n?null:n},n=(n,o)=>{const d=(r=>({allowSelfAsParent:(r=r??{}).allowSelfAsParent??!1,childrenId:r.childrenId??"children",customId:r.customId??"id",parentId:r.parentId??"parentId",rootId:r.rootId??"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__"}))(o);if(!Array.isArray(n))throw new r("Expected an array but got an invalid argument.");const s=((t,n)=>{const o={};for(const e of t){const t=e[n.customId];if(null!=t&&Object.hasOwn(o,t))throw new r(`Duplicate node id "${t}" detected.`);o[t]=e}return t.reduce((t,d)=>{const s=e(d,n.customId);let c=e(d,n.parentId);if(c===s){if(!n.allowSelfAsParent)throw new r(`Node "${s}" cannot be its own parent (self reference found).`);c=n.rootId}return c&&Object.hasOwn(o,c)||(c=n.rootId),c&&Object.hasOwn(t,c)?(t[c].push(d),t):(t[c]=[d],t)},{})})(n,d);return t(s,s[d.rootId],d.customId,d.childrenId)};export{r as ArrayToTreeError,n as arrayToTree};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/errors/array-to-tree-error.ts","../../../../src/internal/create-tree.ts","../../../../src/internal/get.ts","../../../../src/utils/array-to-tree.ts","../../../../src/internal/resolve-options.ts","../../../../src/internal/group-by-parents.ts"],"sourcesContent":["export class ArrayToTreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ArrayToTreeError\";\n }\n}\n","export const createTree = (\n grouped: Record<string, any[]>,\n rootNodes: any[],\n customId: string,\n childrenProperty: string,\n) => {\n if (!Array.isArray(rootNodes)) {\n return [];\n }\n\n return rootNodes.map((node) => {\n const newNode = { ...node };\n const children = grouped[node[customId]];\n if (children) {\n newNode[childrenProperty] = createTree(\n grouped,\n children,\n customId,\n childrenProperty,\n );\n }\n return newNode;\n });\n};\n","/**\n * This code is modified from the radash project.\n * @see https://github.com/sodiray/radash\n */\nexport const get = (value: any, path: string) => {\n const segments = path.split(/[.[\\]]/g);\n let current: any = value;\n for (const key of segments) {\n if (current === null) return null;\n if (current === undefined) return null;\n const dequoted = key.replace(/['\"]/g, \"\");\n if (dequoted.trim() === \"\") continue;\n current = current[dequoted];\n }\n if (current === undefined) return null;\n return current;\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport { createTree, groupByParents, resolveOptions } from \"../internal\";\nimport type {\n ArrayToTreeOptions,\n NormalizedArrayToTreeOptions,\n} from \"../types\";\n\nexport const arrayToTree = (\n data: Record<string, any>[],\n options?: ArrayToTreeOptions,\n) => {\n const normalizeOptions = resolveOptions(\n options,\n ) as NormalizedArrayToTreeOptions;\n\n if (!Array.isArray(data)) {\n throw new ArrayToTreeError(\n \"Expected an array but got an invalid argument.\",\n );\n }\n\n const grouped = groupByParents(data, normalizeOptions);\n\n return createTree(\n grouped,\n grouped[normalizeOptions.rootId],\n normalizeOptions.customId,\n normalizeOptions.childrenId,\n );\n};\n","import type { ArrayToTreeOptions } from \"../types\";\n\nexport const resolveOptions = (\n options?: ArrayToTreeOptions,\n): Required<ArrayToTreeOptions> => {\n options = options ?? {};\n return {\n allowSelfAsParent: options.allowSelfAsParent ?? false,\n childrenId: options.childrenId ?? \"children\",\n customId: options.customId ?? \"id\",\n parentId: options.parentId ?? \"parentId\",\n rootId: options.rootId ?? \"__ARRAY_TO_TREE_VIRTUAL_ROOT_ID__\",\n };\n};\n","import { ArrayToTreeError } from \"../errors\";\nimport type { NormalizedArrayToTreeOptions } from \"../types\";\nimport { get } from \"./get\";\n\nexport const groupByParents = (\n array: Record<string, any>[],\n options: NormalizedArrayToTreeOptions,\n) => {\n const arrayById: Record<string, any> = {};\n for (const item of array) {\n const key = item[options.customId];\n if (key != null && Object.hasOwn(arrayById, key)) {\n throw new ArrayToTreeError(`Duplicate node id \"${key}\" detected.`);\n }\n arrayById[key] = item;\n }\n\n return array.reduce((prev, item) => {\n const id = get(item, options.customId);\n let parentId = get(item, options.parentId);\n\n // Handle self-referencing nodes\n if (parentId === id) {\n if (!options.allowSelfAsParent) {\n throw new ArrayToTreeError(\n `Node \"${id}\" cannot be its own parent (self reference found).`,\n );\n }\n parentId = options.rootId;\n }\n\n if (!parentId || !Object.hasOwn(arrayById, parentId)) {\n parentId = options.rootId;\n }\n\n if (parentId && Object.hasOwn(prev, parentId)) {\n prev[parentId].push(item);\n return prev;\n }\n\n prev[parentId] = [item];\n return prev;\n }, {});\n};\n"],"names":["ArrayToTreeError","Error","constructor","message","super","this","name","createTree","grouped","rootNodes","customId","childrenProperty","Array","isArray","map","node","newNode","children","get","value","path","segments","split","current","key","undefined","dequoted","replace","trim","arrayToTree","data","options","normalizeOptions","allowSelfAsParent","childrenId","parentId","rootId","resolveOptions","array","arrayById","item","Object","hasOwn","reduce","prev","id","push","groupByParents"],"mappings":"AAAM,MAAOA,UAAyBC,MACpC,WAAAC,CAAYC,GACVC,MAAMD,GACNE,KAAKC,KAAO,kBACd,ECJK,MAAMC,EAAa,CACxBC,EACAC,EACAC,EACAC,IAEKC,MAAMC,QAAQJ,GAIZA,EAAUK,IAAKC,IACpB,MAAMC,EAAU,IAAKD,GACfE,EAAWT,EAAQO,EAAKL,IAS9B,OARIO,IACFD,EAAQL,GAAoBJ,EAC1BC,EACAS,EACAP,EACAC,IAGGK,IAdA,GCHEE,EAAM,CAACC,EAAYC,KAC9B,MAAMC,EAAWD,EAAKE,MAAM,WAC5B,IAAIC,EAAeJ,EACnB,IAAK,MAAMK,KAAOH,EAAU,CAC1B,GAAgB,OAAZE,EAAkB,OAAO,KAC7B,QAAgBE,IAAZF,EAAuB,OAAO,KAClC,MAAMG,EAAWF,EAAIG,QAAQ,QAAS,IACd,KAApBD,EAASE,SACbL,EAAUA,EAAQG,GACpB,CACA,YAAgBD,IAAZF,EAA8B,KAC3BA,GCRIM,EAAc,CACzBC,EACAC,KAEA,MAAMC,ECTsB,CAC5BD,IAGO,CACLE,mBAFFF,EAAUA,GAAW,CAAA,GAEQE,oBAAqB,EAChDC,WAAYH,EAAQG,YAAc,WAClCxB,SAAUqB,EAAQrB,UAAY,KAC9ByB,SAAUJ,EAAQI,UAAY,WAC9BC,OAAQL,EAAQK,QAAU,sCDAHC,CACvBN,GAGF,IAAKnB,MAAMC,QAAQiB,GACjB,MAAM,IAAI9B,EACR,kDAIJ,MAAMQ,EEjBsB,EAC5B8B,EACAP,KAEA,MAAMQ,EAAiC,CAAA,EACvC,IAAK,MAAMC,KAAQF,EAAO,CACxB,MAAMd,EAAMgB,EAAKT,EAAQrB,UACzB,GAAW,MAAPc,GAAeiB,OAAOC,OAAOH,EAAWf,GAC1C,MAAM,IAAIxB,EAAiB,sBAAsBwB,gBAEnDe,EAAUf,GAAOgB,CACnB,CAEA,OAAOF,EAAMK,OAAO,CAACC,EAAMJ,KACzB,MAAMK,EAAK3B,EAAIsB,EAAMT,EAAQrB,UAC7B,IAAIyB,EAAWjB,EAAIsB,EAAMT,EAAQI,UAGjC,GAAIA,IAAaU,EAAI,CACnB,IAAKd,EAAQE,kBACX,MAAM,IAAIjC,EACR,SAAS6C,uDAGbV,EAAWJ,EAAQK,MACrB,CAMA,OAJKD,GAAaM,OAAOC,OAAOH,EAAWJ,KACzCA,EAAWJ,EAAQK,QAGjBD,GAAYM,OAAOC,OAAOE,EAAMT,IAClCS,EAAKT,GAAUW,KAAKN,GACbI,IAGTA,EAAKT,GAAY,CAACK,GACXI,IACN,CAAA,IFrBaG,CAAejB,EAAME,GAErC,OAAOzB,EACLC,EACAA,EAAQwB,EAAiBI,QACzBJ,EAAiBtB,SACjBsB,EAAiBE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare class ArrayToTreeError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface ArrayToTreeOptions {
|
|
6
|
+
childrenId?: string;
|
|
7
|
+
customId?: string;
|
|
8
|
+
parentId?: string;
|
|
9
|
+
rootId?: string;
|
|
10
|
+
allowSelfAsParent?: boolean;
|
|
11
|
+
}
|
|
12
|
+
type NormalizedArrayToTreeOptions = Required<ArrayToTreeOptions>;
|
|
13
|
+
|
|
14
|
+
declare const arrayToTree: (data: Record<string, any>[], options?: ArrayToTreeOptions) => any[];
|
|
15
|
+
|
|
16
|
+
export { ArrayToTreeError, arrayToTree };
|
|
17
|
+
export type { ArrayToTreeOptions, NormalizedArrayToTreeOptions };
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": {
|
|
3
|
+
"email": "narukeu@outlook.com",
|
|
4
|
+
"name": "Luke Na",
|
|
5
|
+
"url": "https://narukeu.github.io"
|
|
6
|
+
},
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/okutils/array-to-tree/issues"
|
|
9
|
+
},
|
|
10
|
+
"description": "A TypeScript utility to convert a flat array into a hierarchical tree via parent-child relationships.",
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@biomejs/biome": "2.2.5",
|
|
13
|
+
"@jest/globals": "^30.2.0",
|
|
14
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
15
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
16
|
+
"@types/node": "^24.6.2",
|
|
17
|
+
"jest": "^30.2.0",
|
|
18
|
+
"rimraf": "^6.0.1",
|
|
19
|
+
"rollup": "^4.52.4",
|
|
20
|
+
"rollup-plugin-dts": "^6.2.3",
|
|
21
|
+
"rollup-plugin-node-externals": "^8.1.1",
|
|
22
|
+
"ts-jest": "^29.4.4",
|
|
23
|
+
"tslib": "^2.8.1",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
"import": "./dist/esm/index.js",
|
|
31
|
+
"require": "./dist/cjs/index.js",
|
|
32
|
+
"types": "./dist/types/index.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"homepage": "https://github.com/okutils/array-to-tree",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"array",
|
|
40
|
+
"tree",
|
|
41
|
+
"typescript",
|
|
42
|
+
"utility"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"name": "@okutils/array-to-tree",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/okutils/array-to-tree.git"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "pnpm clean && rollup -c",
|
|
55
|
+
"clean": "rimraf dist",
|
|
56
|
+
"dev": "rollup -c -w",
|
|
57
|
+
"format": "biome format --write",
|
|
58
|
+
"lint": "biome check .",
|
|
59
|
+
"lint:fix": "biome check --write .",
|
|
60
|
+
"test": "jest",
|
|
61
|
+
"type-check": "tsc --noEmit"
|
|
62
|
+
},
|
|
63
|
+
"type": "module",
|
|
64
|
+
"version": "0.0.1-alpha.1"
|
|
65
|
+
}
|