{"_id":"@arnesfield/unnest","_rev":"4-fb0bcfed757eef081fc6ac8867c6e35c","time":{"created":"2022-05-09T09:08:40.092Z","0.0.1":"2022-05-03T13:26:00.725Z","modified":"2022-05-09T09:12:00.646Z","0.0.2-alpha.1":"2022-05-09T09:08:40.309Z","0.0.2":"2022-05-09T09:12:00.569Z"},"name":"@arnesfield/unnest","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.2-alpha.1":{"name":"@arnesfield/unnest","version":"0.0.2-alpha.1","description":"Flatten nested objects to table rows.","keywords":["unnest","flat","flatten","array","object","nested","nested-object","table","tabular","rows","cells","filter","sort"],"homepage":"https://github.com/Arnesfield/unnest#readme","bugs":{"url":"https://github.com/Arnesfield/unnest/issues"},"repository":{"type":"git","url":"git+https://github.com/Arnesfield/unnest.git"},"license":"MIT","author":{"name":"Jefferson Rylee","email":"rylee.jeff385@gmail.com"},"main":"lib/index.js","types":"lib/index.d.ts","scripts":{"prebuild":"rimraf lib","build":"tsc","lint":"eslint . --ext .js,.ts","lint:fix":"npm run lint -- --fix","start":"npm run build -- -w","test":"echo \"Error: no test specified\" && exit 1"},"devDependencies":{"@typescript-eslint/eslint-plugin":"^5.21.0","@typescript-eslint/parser":"^5.21.0","eslint":"^8.14.0","rimraf":"^3.0.2","typescript":"^4.6.4"},"gitHead":"e769bd0c5ad5d326ec580e7e785ae26865a6419c","_id":"@arnesfield/unnest@0.0.2-alpha.1","_nodeVersion":"16.15.0","_npmVersion":"8.5.5","dist":{"integrity":"sha512-TCjqzKvlAAgUJfK4dzg4DjcV4UHCYnN01K7PrjnzArQyXbTcgsz4rvyTm1umlYXrI8NFcY+2FzwM/10q8iCPpQ==","shasum":"5f84873132f3f605d1e255bb4465c7d64b78ac3d","tarball":"https://registry.npmjs.org/@arnesfield/unnest/-/unnest-0.0.2-alpha.1.tgz","fileCount":33,"unpackedSize":39565,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCjEtz5Qzzf787EQ5HRz9cdoYUyCNrzYbPJjVI1uO64YwIhAOE1GHha+o9zzzrpYw+1gADdSGpuin82AKvF8b6x2Em6"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJieNoYACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrWoQ//aT3VAmTljevJkdL+QcJiTFIouX64OQvCvhFYMuIRX5sO6/A0\r\nquGb/t0hrf1XMKVQW8Gf8hCw2SaVyuvIXF/cH6aY2iHd0O2r0Oelq4KxQDwG\r\nzMDF5mS3rXe0hlw2khdoGN1is/vtL0R8CJE4HtqFEnTBS0brQrOqLsUnL9Cl\r\nAyXyOO0CRr8wZEq5UHP0p6B5bN1ZaRNgJP9ZwWOwOdWJ6qNUkqlCmg5DsfTg\r\n/Oj0uifnyOcHq9V99zhQEznAaUNnymOWgX8z65H7Fvg6oiooaQDSxj5gVEJI\r\nfujKoBJNdwxaswSvqQOk6TPcOQ/ckqhANkAN+X4Hjx+kGWvAXHb4JprgGFwN\r\n0kZ83b+/41BMufynecjSUUZV0vdCyVD6vIECW9fZu/IpntFNahysys420w9F\r\nLi48Zqhh2pSDb/zFJUBXQDsMqcN8fOfbKMjih6U59wa7Ype5ZekQixKDbL+z\r\nWcn3KbsSNk6FkZvtX0gx2oQI73FxoSCYvTCzR9JdyPhtG3yUMtXpAezDXC8z\r\neIxyqB7+lpHZjZUP4U+Ul1hvL/Fq+VRPnMk4lw5YcrwQaz1xrldfAshuGrTF\r\nE1z+l2m97rk2Js104RCPbNN96H0weTCpihhy+szhfiOIk/DJtXnEy4Yb9AnD\r\n1Pl0Bg/LQOb26huvYLWLC0HHuKefwVpAjoo=\r\n=Psn5\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"arnesfield","email":"rylee.jeff385@gmail.com"},"directories":{},"maintainers":[{"name":"arnesfield","email":"rylee.jeff385@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/unnest_0.0.2-alpha.1_1652087320151_0.0059781592510999015"},"_hasShrinkwrap":false},"0.0.2":{"name":"@arnesfield/unnest","version":"0.0.2","description":"Flatten nested objects to table rows.","keywords":["unnest","flat","flatten","array","object","nested","nested-object","table","tabular","rows","cells","filter","sort"],"homepage":"https://github.com/Arnesfield/unnest#readme","bugs":{"url":"https://github.com/Arnesfield/unnest/issues"},"repository":{"type":"git","url":"git+https://github.com/Arnesfield/unnest.git"},"license":"MIT","author":{"name":"Jefferson Rylee","email":"rylee.jeff385@gmail.com"},"sideEffects":false,"exports":{"import":"./lib/esm/index.js","require":"./lib/cjs/index.js","default":"./lib/esm/index.js"},"main":"lib/cjs/index.js","module":"lib/esm/index.js","browser":"lib/index.umd.js","types":"lib/types/index.d.ts","scripts":{"prebuild":"rimraf lib","build":"tsc --build tsconfig.lib.json && rollup -c","lint":"eslint . --ext .js,.ts","lint:fix":"npm run lint -- --fix","start":"npm run build -- -w","test":"echo \"Error: no test specified\" && exit 1"},"devDependencies":{"@rollup/plugin-typescript":"^8.3.2","@typescript-eslint/eslint-plugin":"^5.22.0","@typescript-eslint/parser":"^5.22.0","eslint":"^8.15.0","rimraf":"^3.0.2","rollup":"^2.72.1","typescript":"^4.6.4"},"gitHead":"36981036c50f05b898b22fa2e43e2725be1ba565","_id":"@arnesfield/unnest@0.0.2","_nodeVersion":"16.15.0","_npmVersion":"8.5.5","dist":{"integrity":"sha512-76wG6Y5vjRCy5rH/J2NrYiZ3QcJSrudSEjJ1A3rd3BjXDAatEgV+lhtvJFNKlAMGm5iD6tUiq7wtb6qx2xK2Hw==","shasum":"f13b776ea4d59cc0d16ba6ccba9ac069a1ff301b","tarball":"https://registry.npmjs.org/@arnesfield/unnest/-/unnest-0.0.2.tgz","fileCount":59,"unpackedSize":114875,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD5SNBl4tCDb+25PMIxq270c8tatV88VPpN6wvplcE5ZAIhAPCkpH0KiI+ezHqhMsWg28RA7Vn6wC/PSNkX2AdQ0WDL"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJieNrgACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmp/8Q/9EJ915f1TmOcjuKCGgHUDGmHZ3yNtxAtv8XRyl+oumTza6L8z\r\nK2ZUVXxT4FpJRg0HdECD+9yHDB0CnupsfktK3jCpWNlv5MZB/qwJiSVPHNYb\r\nyCLHLPQUOosKRXnp0rSVItB7aJ1tKK4bzYMJgFU8Y0zs037aR6ij/xKaYTuC\r\n7nWRzrL8kkbErRp2DhRxE1iUXaIvJYxEPntkeUY7M5j3052ul4qDb6Tkf8JJ\r\n2/FYXL82BT7Xb10+LHMl/CIlrHCAUSHI3lJxLF0xMfaQDwro0+NsBd72C1sN\r\nt/RXaLo7Yyyi/tcrWpPdnew6AHq/nLt9qnVMosCpC5DCtON8WeGv4buqzRrl\r\nh255IyBa/1ef95hnHUzJYZ4+xb4YaIkF/QJ1Nz3R+74Q4BvRIY3IHM+VE/ZH\r\nrSP7IsJufAWjYdCKsm823wgdcCMszHjmyGPWp5CmCeUDscz3p+nWZ7LNb1Sy\r\nMjA98ZdAzGW4OVks2OmgXGSU9J/RYB19S6U/QEwwCwR7RlguisebFy2EYxXS\r\nXvm+nClfkQJl1/9GK83YXWK4Mv3iynUzK8snxWbdWjwtiW6M+WnAJQfy3ajf\r\neDCNasUinTaQBAOVARqIxnRAhlvyYGgXP8vbEsS/BSWsCvGvYWr42NKK2Gqj\r\nQQtTZnVzyDD1BlamtrdL/vMYSCmwJFxQfAE=\r\n=UKkO\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"arnesfield","email":"rylee.jeff385@gmail.com"},"directories":{},"maintainers":[{"name":"arnesfield","email":"rylee.jeff385@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/unnest_0.0.2_1652087520388_0.1220505584815792"},"_hasShrinkwrap":false}},"maintainers":[{"name":"arnesfield","email":"rylee.jeff385@gmail.com"}],"description":"Flatten nested objects to table rows.","homepage":"https://github.com/Arnesfield/unnest#readme","keywords":["unnest","flat","flatten","array","object","nested","nested-object","table","tabular","rows","cells","filter","sort"],"repository":{"type":"git","url":"git+https://github.com/Arnesfield/unnest.git"},"author":{"name":"Jefferson Rylee","email":"rylee.jeff385@gmail.com"},"bugs":{"url":"https://github.com/Arnesfield/unnest/issues"},"license":"MIT","readme":"# unnest\n\nFlatten nested objects to table rows.\n\n```javascript\nconst { unnest } = require('@arnesfield/unnest');\n```\n\n```javascript\nconst table = unnest(items).by(property);\nconst rows = table.rows();\nconst data = table.data();\n```\n\nUsing [TypeScript](https://www.typescriptlang.org/):\n\n```typescript\nconst table: Table<Schema> = unnest(items).by<Schema>(property);\n```\n\n> **Tip**: Setting the `Schema` generic type should improve typings for `Row`s, `Cell`s, and `RowData`.\n\n## Installation\n\n```sh\nnpm install @arnesfield/unnest\n```\n\nUse the module:\n\n```javascript\n// ES6\nimport unnest from '@arnesfield/unnest';\n\n// CommonJS\nconst { unnest } = require('@arnesfield/unnest');\n```\n\nUse the [UMD](https://github.com/umdjs/umd) build:\n\n```html\n<script src=\"https://unpkg.com/@arnesfield/unnest/lib/index.umd.js\"></script>\n```\n\n```javascript\nconst table = window.unnest(data).by(property);\n```\n\n## Usage\n\nHere is a basic example of `unnest`ing a nested object:\n\n```javascript\nconst user = {\n  email: 'john.doe@foo.bar',\n  animals: [\n    { type: 'cat', food: ['fish', 'meat'] },\n    { type: 'frog', food: ['insects'] }\n  ]\n};\n```\n\nUse `unnest` to flatten the object:\n\n```javascript\nconst table = unnest(user).by({\n  animals: {\n    food: true\n  }\n});\n```\n\n> **Tip**: Notice that the structure of the `property` value is similar to the nested object.\n\nThe `table` contains the `Row`s or `RowData` of the `unnest`ed object:\n\n```javascript\n// get rows\nconst rows = table.rows();\n\n// get data\nconst data = table.data();\n```\n\nOutput of `table.data()`:\n\n> **Note**: Most of the actual output structure is omitted for brevity.\n\n```javascript\n[\n  { root: /* user */, animals: /* cat */,  food: /* fish */    },\n  {                                        food: /* meat */    },\n  {                   animals: /* frog */, food: /* insects */ },\n]\n```\n\nUsing a table, the result would look something like this:\n\n| root | animals | food    |\n| ---- | ------- | ------- |\n| user | cat     | fish    |\n|      |         | meat    |\n|      | frog    | insects |\n\nIf you're using [TypeScript](https://www.typescriptlang.org/), the `Schema` type (similar to `RowData`) would look something like this:\n\n```typescript\ninterface Schema {\n  root: User;\n  animals: Animal;\n  food: Food;\n}\n\nconst table = unnest(user).by<Schema>(property);\n```\n\n### `unnest` function and `Property`\n\nThe `unnest` function takes in the data (array or object) and calling `.by(property)` returns a `table`:\n\n```javascript\nconst table = unnest(data).by(property);\n```\n\nThe `property` value structure is based on the data passed to the `unnest` function.\n\n```typescript\ntype PropertyValue = string | boolean | Property;\n\ninterface Property {\n  // name of the property, defaults to the object property key or `root`\n  name?: string;\n\n  // other properties based on the data\n  [property]: PropertyValue;\n}\n```\n\nConsider the following interface:\n\n```typescript\ninterface User {\n  email: string;\n  aliases: string[];\n  animals: {\n    type: string;\n    food: {\n      kind: string;\n      value: string[];\n    }[];\n  }[];\n  groups?: {\n    title: string;\n    members: string[];\n  }[];\n}\n```\n\nThe `property` value type may look like the following depending on how you want to `unnest` the object:\n\n```typescript\n{\n  // name: string,\n  email: PropertyValue,\n  aliases: PropertyValue,\n  animals: {\n    // name: string,\n    type: PropertyValue,\n    food: {\n      // name: string,\n      kind: PropertyValue,\n      value: PropertyValue\n    }\n  },\n  groups: {\n    // name: string,\n    title: PropertyValue,\n    members: PropertyValue\n  }\n}\n```\n\nEach specified property will be included in the `Row` and `RowData` object.\n\n### Custom Column Name (`property.name`)\n\nBy default, the object property keys are used as the default column name (`root` is the default for the main object) similar to our example output a while back:\n\n| root | animals | food    |\n| ---- | ------- | ------- |\n| user | cat     | fish    |\n|      |         | meat    |\n|      | frog    | insects |\n\nNotice that the column names are `root`, `animals`, and `food`.\n\nYou can configure the column names by using the `name` property, or pass it as the property value:\n\n```javascript\nconst table = unnest(user).by({\n  // root -> owner\n  name: 'owner',\n  animals: {\n    // animals -> pet\n    name: 'pet',\n    // food -> treat\n    food: 'treat' // can also be `food: { name: 'treat' }`\n  }\n});\n```\n\nOutput of `table.data()` using a table:\n\n| owner | pet  | treat   |\n| ----- | ---- | ------- |\n| user  | cat  | fish    |\n|       |      | meat    |\n|       | frog | insects |\n\nNotice that the columns are using the custom names.\n\nSince the column names have changed, make sure the `Schema` type gets updated accordingly:\n\n```typescript\ninterface Schema {\n  // root -> owner\n  owner: User;\n  // animals -> pet\n  pet: Animal;\n  // food -> treat\n  treat: Food;\n}\n```\n\n### `Row`, `Cell`, and `RowData`\n\nBefore jumping in to the `Table` object, we'll need to know what are `Row`s, `Cell`s, and `RowData`.\n\n```typescript\ninterface Row {\n  group: string | number;\n  cells: {\n    [property]: Cell;\n  };\n}\n\ninterface Cell {\n  data: /* cell data type */;\n  group: string | number;\n  span?: number;\n}\n\ntype RowData<Schema> = Partial<Schema>;\n```\n\nWhat do these mean?\n\n- `Row` - contains the `Cell`s.\n- `Cell` - contains the data.\n- `RowData` - the `Schema` but with partial values.\n- `span` - pertains to the `rowspan` of a `Cell`. It is set only for `Cell`s that span across `Row`s.\n- `group` - contains a unique value which determines if `Row`s or `Cell`s are related (or are in a `group`).\n\n  By default, the `group` value uses the `index` of the array of `data` passed to `unnest` (if it's an object, the value is `0`).\n\n  You can set your own `group` value through the `unnest` function:\n\n  ```javascript\n  unnest(users, (user, index, array) => user.email).by(property);\n  ```\n\n  > **Tip**: The `user.email` is used as the `group` value.\n\n### `Table`\n\nUsing `unnest(data).by(property)` gives you a `Table` object.\n\nThe `Table` object contains the `Row`s and `RowData` that have been `unnest`ed, as well as other useful methods.\n\n- Get the rows.\n\n  ```javascript\n  const rows = table.rows();\n\n  // filter by group\n  const rows = table.rows(group);\n  ```\n\n- Get the row data.\n\n  ```javascript\n  const data = table.data();\n  ```\n\n- Transform `Row`s to `RowData`.\n\n  ```javascript\n  const data = table.data(...rows);\n  ```\n\n- Get the root rows (the main object/s or the first rows per group).\n\n  ```javascript\n  const rows = table.roots();\n  ```\n\n- Get all the cells in the column (property).\n\n  ```javascript\n  const cells = table.column('treat');\n\n  // filter by group\n  const cells = table.column('treat', group);\n\n  // set `includeEmpty` to `true` to include `undefined` cells\n  const cells = table.column('treat', group, true);\n  ```\n\n  > **Tip**: See `treat` property from the previous example.\n\n- Get the cell info (current, previous, and next cells) at row index if any.\n\n  ```javascript\n  const rowIndex = 1;\n  const info = table.cell('treat', rowIndex);\n  ```\n\n  Output of `info`:\n\n  ```javascript\n  {\n    current: /* Cell */ { data: 'meat', group: 0 },\n    previous: /* Cell */ { data: 'fish', group: 0 },\n    next: /* Cell */ { data: 'insects', group: 0 }\n  }\n  ```\n\n- `table.filter(callback)`\n\n  Similar to `array.filter(callback)`, but `table.filter(callback)` will return a new `Table` object with the filtered rows.\n\n  The return value of the filter callback is an object similar to the `Schema` type.\n\n  ```javascript\n  const filteredTable = table.filter((row, index, array) => {\n    return {\n      owner: /* true, false, undefined */ true,\n      pet: /* true, false, undefined */ true,\n      treat: /* true, false, undefined */ true\n    };\n  });\n  const filteredRows = filteredTable.rows();\n  ```\n\n- `table.sort(compareFn)`\n\n  Similar to `array.sort(compareFn)`, but only the root rows are used as the arguments for the `compareFn`.\n\n  The return value of `table.sort(compareFn)` is also a new `Table` object similar to `table.filter()`.\n\n  ```javascript\n  const sortedTable = table.sort((rootRowA, rootRowB) => {\n    return /* number */ 0;\n  });\n  const sortedRows = sortedTable.rows();\n  ```\n\n  By using the root rows as the arguments to compare, the other rows of the same group do not get sorted. Only the entire group is sorted against other groups.\n\n  e.g. After sorting, the rows with `group` index `1` precede the rows with `group` index `0`.\n\n  > **Tip**: The methods `table.filter()` and `table.sort()` return a new `Table` object to allow the usage of the `Table` methods on the new filtered/sorted rows instead.\n\n- Update `Cell` span values.\n\n  ```javascript\n  table.updateSpans();\n  ```\n\n  Note that this will change the `rows` array, `row`, and `cell` references.\n\n### Merging Columns\n\nThere may be cases where the nested object would require its properties to be in one column.\n\nThis is already handled by `unnest` by placing the incoming cells last.\n\nConsider this nested object:\n\n```javascript\nconst user = {\n  email: 'john.doe@foo.bar',\n  animals: [\n    { type: 'cat', food: ['fish', 'meat'] },\n    { type: 'frog', food: ['insects'] }\n  ],\n  food: ['chicken', 'beef']\n};\n```\n\nNotice that there is `food` property for `animals` and the `user` object itself. Let's try to `unnest` this object:\n\n```javascript\nconst table = unnest(user).by({\n  animals: {\n    food: true\n  },\n  food: true\n});\n```\n\nOutput of `table.data()` using a table:\n\n| root | animals | food    |\n| ---- | ------- | ------- |\n| user | cat     | fish    |\n|      |         | meat    |\n|      | frog    | insects |\n|      |         | chicken |\n|      |         | beef    |\n\nThe `user.food` values (`chicken` and `beef`) come after the previous rows.\n\nThis merge feature should work in most cases as long as the property values are arranged in a way that works for you.\n\n> **Tip**: You can use a different column name for duplicating property names so they show up in a different column.\n\n### Special Cases\n\nIf the resulting output does not satisfy your needs, then you are free to directly mutate the `rows` array of `table`.\n\n```javascript\nconst rows = table.rows();\n\n// mutate `rows` array directly, some examples:\nrows.pop();\nrows.push(row);\nrows.sort(sortFn);\nrows.splice(spliceFn);\n\n// update cell span values\ntable.updateSpans();\n```\n\nNote that `rows` (also `row`s and `cell`s) will have a different reference after calling `table.updateSpans()`.\n\n```javascript\nrows === table.rows(); // false\n```\n\nThis method will allow you to merge 2 or more `table.rows()`. Directly updating `rows` means you would have to take note of the `group` value uniqueness.\n\n### `render` function\n\n```javascript\nrender(rows, getLabelFn);\nrender(rows, columns, getLabelFn);\n```\n\nA `render` function is included which accepts rows and returns a Markdown table `string`.\n\n```javascript\nconst { unnest, render } = require('@arnesfield/unnest');\n// ...\nconst tableStr = render(table.rows(), row => {\n  // convert to RowData so it's easier to work with\n  const [data] = table.data(row);\n  // labels per column, defaults to empty string\n  return {\n    owner: data.owner?.email,\n    pet: data.pet?.type,\n    treat: data.treat\n  };\n});\nconsole.log(tableStr);\n```\n\nOutput:\n\n```markdown\n| owner            | pet  | treat   |\n| ---------------- | ---- | ------- |\n| john.doe@foo.bar | cat  | fish    |\n|                  |      | meat    |\n|                  | frog | insects |\n```\n\nYou can also pass in default columns to use. With this, you can reorder the columns to display:\n\n```javascript\nconst tableStr = render(table.rows(), ['treat', 'owner', 'pet'], row => {\n  const [data] = table.data(row);\n  return {\n    owner: data.owner?.email,\n    pet: data.pet?.type,\n    treat: data.treat\n  };\n});\nconsole.log(tableStr);\n```\n\nOutput:\n\n```markdown\n| treat   | owner            | pet  |\n| ------- | ---------------- | ---- |\n| fish    | john.doe@foo.bar | cat  |\n| meat    |                  |      |\n| insects |                  | frog |\n```\n\n## License\n\nLicensed under the [MIT License](LICENSE).\n","readmeFilename":"README.md"}