Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ You can provide a second argument to arrayToTree with configuration options. Rig
- `dataField`: Key which will contain all properties/data of the original items. Set to null if you don't want a container. Default: `"data"`
- `throwIfOrphans`: Option to throw an error if the array of items contains one or more items that have no parents in the array or if the array of items contains items with a circular parent/child relationship. This option has a small runtime penalty, so it's disabled by default. When enabled, the function will throw an error containing the parentIds that were not found in the items array, or in the case of only circular item relationships a generic error. The function will throw an error if the number of nodes in the tree is smaller than the number of nodes in the original array. When disabled, the function will just ignore orphans and circular relationships and not add them to the tree. Default: `false`
- `rootParentIds`: Object with parent ids as keys and `true` as values that should be considered the top or root elements of the tree. This is useful when your tree is a subset of full tree, which means there is no item whose parent id is one of `undefined`, `null` or `''`. The array you pass in will be replace the default value. `undefined` and `null` are always considered to be rootParentIds. For more details, see [#23](https://github.com/philipstanislaus/performant-array-to-tree/issues/23). Default: `{'': true}`
- `assign`: Option that enables `Object.assign` instead of the spread operator to create an item in the tree when `dataField` is `null`. This is useful if your items have a prototype that should be maintained. If enabled and `dataField` is `null`, the original node item will be used, and the `children` property will be assigned, calling any setters on that field. If `dataField` is not `null`, this option has no effect, since the original node will be used under the `dataField` of a new object. If you are unsure whether you need to enable this, it's likely fine to leave it disabled. Default: `false`

Example:

Expand Down
1 change: 1 addition & 0 deletions build/arrayToTree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Config {
[rootParentId: string]: true;
};
nestedIds: boolean;
assign: boolean;
}
/**
* Unflattens an array to a tree with runtime O(n)
Expand Down
12 changes: 9 additions & 3 deletions build/arrayToTree.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/arrayToTree.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/arrayToTree.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions build/arrayToTree.spec.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/arrayToTree.spec.js.map

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions src/arrayToTree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,65 @@ describe("arrayToTree", () => {
},
]);
});

it("should keep prototype if assign is enabled", () => {
const animal = {
legs() {
return 4;
},
};

const mom = Object.create(animal);
mom.id = "mom";
mom.parentId = null;
const kitty = Object.create(animal);
kitty.id = "kitty";
kitty.parentId = "mom";

const tree = arrayToTree([mom, kitty], { dataField: null, assign: true });

expect(tree).to.deep.equal([mom]);

expect(tree[0].__proto__).to.deep.equal(animal);

expect(tree[0].legs()).to.equal(4);
});

it("should not keep prototype if assign is disabled", () => {
const animal = {
legs() {
return 4;
},
};

const mom = Object.create(animal);
mom.id = "mom";
mom.parentId = null;
const kitty = Object.create(animal);
kitty.id = "kitty";
kitty.parentId = "mom";

const tree = arrayToTree([mom, kitty], { dataField: null, assign: false });

expect(tree).to.deep.equal([
{
id: "mom",
parentId: null,
children: [
{
id: "kitty",
parentId: "mom",
children: [],
},
],
},
]);

expect(mom.legs()).to.equal(4);

expect(tree[0].__proto__).to.deep.equal(Object.prototype);
expect(tree[0].legs).to.equal(undefined);
});
});

describe("countNodes", () => {
Expand Down
6 changes: 6 additions & 0 deletions src/arrayToTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Config {
throwIfOrphans: boolean;
rootParentIds: { [rootParentId: string]: true }; // use an object here for fast lookups
nestedIds: boolean;
assign: boolean;
}

const defaultConfig: Config = {
Expand All @@ -24,6 +25,7 @@ const defaultConfig: Config = {
throwIfOrphans: false,
rootParentIds: { "": true },
nestedIds: true,
assign: false,
};

/**
Expand Down Expand Up @@ -84,6 +86,10 @@ export function arrayToTree(
// add the current item's data to the item in the lookup table
if (conf.dataField) {
lookup[itemId][conf.dataField] = item;
} else if (conf.assign) {
lookup[itemId] = Object.assign(item, {
[conf.childrenField]: lookup[itemId][conf.childrenField],
});
} else {
lookup[itemId] = {
...item,
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"no-implicit-dependencies": [true, "dev"]
"no-implicit-dependencies": [true, "dev"],
"prefer-object-spread": false
}
}