[
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": \"eslint:recommended\",\n  \"rules\": {\n    \"comma-dangle\": \"off\"\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true,\n    \"mocha\": true\n  },\n  \"parserOptions\": {\n    \"ecmaVersion\": 6\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nbower_components\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"none\"\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: node_js\ncache:\n  directories:\n    - node_modules\nnotifications:\n  email: false\nnode_js:\n  - 'stable'\n  - '6'\nbranches:\n  except:\n    - \"/^v\\\\d+\\\\.\\\\d+\\\\.\\\\d+$/\"\n"
  },
  {
    "path": "index.d.ts",
    "content": "export = arrayToTree;\n\ndeclare function arrayToTree<T>(data: T[], options?: Partial<arrayToTree.Options>): Array<arrayToTree.Tree<T>>;\n\ndeclare namespace arrayToTree {\n\tinterface Options {\n\t\tchildrenProperty: string;\n\t\tparentProperty: string;\n\t\tcustomID: string;\n\t\trootID: string;\n\t}\n\n\ttype Tree<T> = T & {\n\t\tchildren?: Array<Tree<T>>;\n\t};\n}\n"
  },
  {
    "path": "index.js",
    "content": "'use strict';\nvar property = require('nested-property');\nvar keyBy = require('lodash.keyby');\n\nfunction createTree(array, rootNodes, customID, childrenProperty) {\n  var tree = [];\n\n  for (var rootNode in rootNodes) {\n    var node = rootNodes[rootNode];\n    var childNode = array[node[customID]];\n\n    if (!node && !rootNodes.hasOwnProperty(rootNode)) {\n      continue;\n    }\n\n    if (childNode) {\n      node[childrenProperty] = createTree(\n        array,\n        childNode,\n        customID,\n        childrenProperty\n      );\n    }\n\n    tree.push(node);\n  }\n\n  return tree;\n}\n\nfunction groupByParents(array, options) {\n  var arrayByID = keyBy(array, options.customID);\n\n  return array.reduce(function(prev, item) {\n    var parentID = property.get(item, options.parentProperty);\n    if (!parentID || !arrayByID.hasOwnProperty(parentID)) {\n      parentID = options.rootID;\n    }\n\n    if (parentID && prev.hasOwnProperty(parentID)) {\n      prev[parentID].push(item);\n      return prev;\n    }\n\n    prev[parentID] = [item];\n    return prev;\n  }, {});\n}\n\nfunction isObject(o) {\n  return Object.prototype.toString.call(o) === '[object Object]';\n}\n\nfunction deepClone(data) {\n  if (Array.isArray(data)) {\n    return data.map(deepClone);\n  } else if (isObject(data)) {\n    return Object.keys(data).reduce(function(o, k) {\n      o[k] = deepClone(data[k]);\n      return o;\n    }, {});\n  } else {\n    return data;\n  }\n}\n\n/**\n * arrayToTree\n * Convert a plain array of nodes (with pointers to parent nodes) to a nested\n * data structure\n *\n * @name arrayToTree\n * @function\n *\n * @param {Array} data An array of data\n * @param {Object} options An object containing the following fields:\n *\n *  - `parentProperty` (String): A name of a property where a link to\n * a parent node could be found. Default: 'parent_id'\n *  - `customID` (String): An unique node identifier. Default: 'id'\n *  - `childrenProperty` (String): A name of a property where children nodes\n * are going to be stored. Default: 'children'.\n *\n * @return {Array} Result of transformation\n */\n\nmodule.exports = function arrayToTree(data, options) {\n  options = Object.assign(\n    {\n      parentProperty: 'parent_id',\n      childrenProperty: 'children',\n      customID: 'id',\n      rootID: '0'\n    },\n    options\n  );\n\n  if (!Array.isArray(data)) {\n    throw new TypeError('Expected an array but got an invalid argument');\n  }\n\n  var grouped = groupByParents(deepClone(data), options);\n  return createTree(\n    grouped,\n    grouped[options.rootID],\n    options.customID,\n    options.childrenProperty\n  );\n};\n"
  },
  {
    "path": "license.md",
    "content": "Copyright © 2016 Philipp Alferov.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"array-to-tree\",\n  \"version\": \"3.3.2\",\n  \"description\": \"Convert a plain array of nodes (with pointers to parent nodes) to a tree\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"mocha --reporter nyan\",\n    \"tdd\": \"npm test -- --watch\",\n    \"release\": \"npm version ${BUMP:-\\\"patch\\\"} --no-git-tag-version && git add package.json package-lock.json && git commit -m \\\":octocat: Bump to $(cat package.json | json version)\\\" && git tag $(cat package.json | json version)\",\n    \"release:major\": \"BUMP=major npm run release\",\n    \"release:minor\": \"BUMP=minor npm run release\"\n  },\n  \"repository\": \"alferov/array-to-tree\",\n  \"keywords\": [\n    \"array\",\n    \"list\",\n    \"pointer\",\n    \"parent\",\n    \"tree\",\n    \"navigation\",\n    \"nested\"\n  ],\n  \"devDependencies\": {\n    \"chai\": \"^4.1.2\",\n    \"eslint\": \"^5.4.0\",\n    \"json\": \"^9.0.3\",\n    \"mocha\": \"^5.2.0\"\n  },\n  \"author\": {\n    \"name\": \"Philipp Alferov\",\n    \"email\": \"philipp.alferov@gmail.com\"\n  },\n  \"engines\": {\n    \"node\": \">=4\"\n  },\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"lodash.keyby\": \"^4.6.0\",\n    \"nested-property\": \"^0.0.7\"\n  }\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# array-to-tree\n\n[![Build Status][travis-image]][travis-url] [![Dependency Status][depstat-image]][depstat-url]\n\n![array-to-tree](media/array-to-tree.png)\n\n> Convert a plain array of nodes (with pointers to parent nodes) to a nested data structure.\n\nSolves a problem with conversion of retrieved from a database sets of data to a nested data structure (i.e. navigation tree).\n\n## Installation\n\n```bash\n$ npm install array-to-tree --save\n```\n\n## Usage\n\n```js\nvar arrayToTree = require('array-to-tree');\n\nvar dataOne = [\n  {\n    id: 1,\n    name: 'Portfolio',\n    parent_id: undefined\n  },\n  {\n    id: 2,\n    name: 'Web Development',\n    parent_id: 1\n  },\n  {\n    id: 3,\n    name: 'Recent Works',\n    parent_id: 2\n  },\n  {\n    id: 4,\n    name: 'About Me',\n    parent_id: undefined\n  }\n];\n\narrayToTree(dataOne);\n\n/*\n * Output:\n *\n * Portfolio\n *   Web Development\n *     Recent Works\n * About Me\n */\n\nvar dataTwo = [\n  {\n    _id: 'ec654ec1-7f8f-11e3-ae96-b385f4bc450c',\n    name: 'Portfolio',\n    parent: null\n  },\n  {\n    _id: 'ec666030-7f8f-11e3-ae96-0123456789ab',\n    name: 'Web Development',\n    parent: 'ec654ec1-7f8f-11e3-ae96-b385f4bc450c'\n  },\n  {\n    _id: 'ec66fc70-7f8f-11e3-ae96-000000000000',\n    name: 'Recent Works',\n    parent: 'ec666030-7f8f-11e3-ae96-0123456789ab'\n  },\n  {\n    _id: '32a4fbed-676d-47f9-a321-cb2f267e2918',\n    name: 'About Me',\n    parent: null\n  }\n];\n\narrayToTree(dataTwo, {\n  parentProperty: 'parent',\n  customID: '_id'\n});\n\n/*\n * Output:\n *\n * Portfolio\n *   Web Development\n *     Recent Works\n * About Me\n */\n```\n\n## API\n\n### `arrayToTree(data, [options])`\n\nConvert a plain array of nodes (with pointers to parent nodes) to a a nested data structure.\n\n#### Parameters\n\n- **Array** `data`: An array of data\n- **Object** `options`: An object containing the following fields:\n  - `parentProperty` (String): A name of a property where a link to a parent node could be found. Default: 'parent_id'.\n  - `childrenProperty` (String): A name of a property where children nodes are going to be stored. Default: 'children'.\n  - `customID` (String): An unique node identifier. Default: 'id'.\n\n#### Return\n\n- **Array**: Result of transformation\n\n## License\n\nMIT © [Philipp Alferov](https://github.com/alferov)\n\n[travis-url]: https://travis-ci.org/alferov/array-to-tree\n[travis-image]: https://img.shields.io/travis/alferov/array-to-tree.svg?style=flat-square\n[depstat-url]: https://david-dm.org/alferov/array-to-tree\n[depstat-image]: https://david-dm.org/alferov/array-to-tree.svg?style=flat-square\n"
  },
  {
    "path": "test/test.js",
    "content": "'use strict';\nvar chai = require('chai');\nvar expect = chai.expect;\nvar toTree = require('../index.js');\n\nvar initial = [\n  {\n    id: 1,\n    parent_id: null,\n    children: [{ id: 5 }]\n  },\n  {\n    id: 2,\n    parent_id: 1\n  },\n  {\n    id: 3,\n    parent_id: 2\n  },\n  {\n    id: 4,\n    parent_id: null\n  }\n];\n\nvar current;\n\ndescribe('array-to-tree', function() {\n  describe('with default arguments', function() {\n    beforeEach(function() {\n      current = toTree(initial);\n    });\n\n    it('should not modify given array', function() {\n      expect(initial[0]).to.be.deep.equal({\n        id: 1,\n        parent_id: null,\n        children: [{ id: 5 }]\n      });\n    });\n\n    it('should return an array', function() {\n      expect(current).to.be.an('array');\n    });\n\n    it('should keep parent_id property', function() {\n      var first = current[0];\n      expect(first).to.have.property('parent_id');\n    });\n\n    it('should create nested objects with children', function() {\n      var first = current[0];\n      expect(first)\n        .to.have.property('children')\n        .that.is.an('array')\n        .to.have.length.of.at.least(1);\n    });\n\n    it('should return an expected value', function() {\n      expect(current).to.be.deep.equal([\n        {\n          id: 1,\n          parent_id: null,\n          children: [\n            {\n              id: 2,\n              parent_id: 1,\n              children: [\n                {\n                  id: 3,\n                  parent_id: 2\n                }\n              ]\n            }\n          ]\n        },\n        {\n          id: 4,\n          parent_id: null\n        }\n      ]);\n    });\n  });\n\n  describe('with invalid arguments', function() {\n    it('should return an empty array if the empty array passed', function() {\n      expect(toTree([])).to.be.deep.equal([]);\n    });\n\n    it('should throw an error if wrong arguments passed', function() {\n      expect(toTree.bind(null, 'string')).to.throw(/invalid argument/);\n      expect(toTree.bind(null, {})).to.throw(/invalid argument/);\n    });\n\n    it('returns the same array if there is no pointer to parent', function() {\n      var modified = initial.map(function(item) {\n        delete item.parent_id;\n        return item;\n      });\n      expect(toTree(modified)).to.be.deep.equal(modified);\n    });\n  });\n\n  describe('with custom arguments', function() {\n    it('should work with custom parents links', function() {\n      expect(\n        toTree(\n          [\n            {\n              _id: 'ec654ec1-7f8f-11e3-ae96-b385f4bc450c',\n              parent: null\n            },\n            {\n              _id: 'ec666030-7f8f-11e3-ae96-0123456789ab',\n              parent: 'ec654ec1-7f8f-11e3-ae96-b385f4bc450c'\n            },\n            {\n              _id: 'ec66fc70-7f8f-11e3-ae96-000000000000',\n              parent: 'ec666030-7f8f-11e3-ae96-0123456789ab'\n            },\n            {\n              _id: '32a4fbed-676d-47f9-a321-cb2f267e2918',\n              parent: null\n            }\n          ],\n          {\n            parentProperty: 'parent',\n            customID: '_id',\n            childrenProperty: '_children'\n          }\n        )\n      ).to.be.deep.equal([\n        {\n          _id: 'ec654ec1-7f8f-11e3-ae96-b385f4bc450c',\n          parent: null,\n          _children: [\n            {\n              _id: 'ec666030-7f8f-11e3-ae96-0123456789ab',\n              parent: 'ec654ec1-7f8f-11e3-ae96-b385f4bc450c',\n              _children: [\n                {\n                  _id: 'ec66fc70-7f8f-11e3-ae96-000000000000',\n                  parent: 'ec666030-7f8f-11e3-ae96-0123456789ab'\n                }\n              ]\n            }\n          ]\n        },\n        {\n          _id: '32a4fbed-676d-47f9-a321-cb2f267e2918',\n          parent: null\n        }\n      ]);\n    });\n\n    it('should work with nested parent id', function() {\n      expect(\n        toTree(\n          [\n            {\n              id: 1,\n              attributes: {\n                name: 'Parent',\n                parent_id: null\n              }\n            },\n            {\n              id: 2,\n              attributes: {\n                name: 'Child One',\n                parent_id: 1\n              }\n            },\n            {\n              id: 3,\n              attributes: {\n                name: 'Child Two',\n                parent_id: 1\n              }\n            }\n          ],\n          {\n            parentProperty: 'attributes.parent_id'\n          }\n        )\n      ).to.be.deep.equal([\n        {\n          id: 1,\n          attributes: {\n            name: 'Parent',\n            parent_id: null\n          },\n          children: [\n            {\n              id: 2,\n              attributes: {\n                name: 'Child One',\n                parent_id: 1\n              }\n            },\n            {\n              id: 3,\n              attributes: {\n                name: 'Child Two',\n                parent_id: 1\n              }\n            }\n          ]\n        }\n      ]);\n    });\n\n    it('should work with orphan nodes', function() {\n      expect(\n        toTree([\n          {\n            id: 1,\n            parent_id: null\n          },\n          {\n            id: 2,\n            parent_id: 1\n          },\n          {\n            id: 3,\n            parent_id: 2\n          },\n          {\n            id: 4,\n            parent_id: 5\n          }\n        ])\n      ).to.be.deep.equal([\n        {\n          id: 1,\n          parent_id: null,\n          children: [\n            {\n              id: 2,\n              parent_id: 1,\n              children: [\n                {\n                  id: 3,\n                  parent_id: 2\n                }\n              ]\n            }\n          ]\n        },\n        {\n          id: 4,\n          parent_id: 5\n        }\n      ]);\n    });\n  });\n});\n"
  }
]