UNPKG

@almela/obx

Version:

Fast & Lightweight Object Manipulation Library for Javascript

773 lines (592 loc) 16.4 kB
<div id="top"></div> <!-- PROJECT SHIELDS --> <!-- *** I'm using markdown "reference style" links for readability. *** Reference links are enclosed in brackets [ ] instead of parentheses ( ). *** See the bottom of this document for the declaration of the reference variables *** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use. *** https://www.markdownguide.org/basic-syntax/#reference-style-links --> <div align="center"> [![Contributors][contributors-shield]][contributors-url] [![Forks][forks-shield]][forks-url] [![Stargazers][stars-shield]][stars-url] [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] [![LinkedIn][linkedin-shield]][linkedin-url] </div> <!-- PROJECT LOGO --> <br /> <div align="center"> <a href="https://github.com/llGaetanll/obx"> <img src="docs/img/logo.png" alt="Logo" width="80" height="80"> </a> <h3 align="center">@almela/obx</h3> <p align="center"> obx - objects extended <br /> <a href="https://github.com/llGaetanll/obx#docs"><strong>Explore the docs »</strong></a> <br /> <br /> <a href="https://www.npmjs.com/package/@almela/obx">View on NPM</a> · <a href="https://github.com/llGaetanll/obx/issues">Report Bug</a> · <a href="https://github.com/llGaetanll/obx/issues">Request Feature</a> </p> </div> <!-- TABLE OF CONTENTS --> <details> <summary>Table of Contents</summary> <ol> <li><a href="#about">About</a></li> <li><a href="#getting-started">Getting Started</a></li> <ul> <li><a href="#installation">Installation</a></li> <li><a href="#usage">Usage</a></li> </ul> <li><a href="#docs">Docs</a></li> <li><a href="#roadmap">Roadmap</a></li> <li><a href="#contributing">Contributing</a></li> <li><a href="#license">License</a></li> <li><a href="#contact">Contact</a></li> <li><a href="#acknowledgments">Acknowledgments</a></li> </ol> </details> <!-- ABOUT THE PROJECT --> ## About obx is a super fast, tiny, well documented, and battle-tested object manipulation library for Javascript. <p align="right">(<a href="#top">back to top</a>)</p> <!-- INSTALLATION --> ## Getting Started ### Installation Install with npm ```sh npm i @almela/obx ``` or with yarn ```sh yarn add @almela/obx ``` ### Usage In your file, simply add ```js import * as obx from '@almela/obx' ``` or simply import select functions ```js import { eq, cp } from '@almela/obx' ``` <p align="right">(<a href="#top">back to top</a>)</p> <!-- USAGE EXAMPLES --> ## Docs For even more examples, see the [tests](https://github.com/llGaetanll/obx/blob/master/src/index.test.js). ### Functions * [eq(a, b, params)](#eq) * [cp(o, params)](#cp) * [get(o, p)](#get) * [set(o, p, v)](#set) * [len(o, params)](#len) * [map(o, fn, params)](#map) * [reduce(o, fn, a, params)](#reduce) * [zip(objs, params)](#zip) * [sub(o, s, params)](#sub) * [add(o, a, params)](#add) * [isEmptyObj(o)](#isEmptyObj) * [isEmptyArr(a)](#isEmptyArr) ### `eq` Assert that two objects are equal. Objects are equal if they have the same keys and values. **Params** - `a` : <code>Object</code> - *Object 1* - `b` : <code>Object</code> - *Object 2* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of equality check. Defaults to infinity* <details> <summary><b>Examples</b></summary> Object order doesn&#x27;t matter ```js obx.eq( { foo: "bar", bar: "baz" }, { bar: "baz", foo: "bar" } ) // -> true ``` With Arrays ```js obx.eq([1, 2, 3], [1, 2, 3]) // -> true ``` Array order does matter! ```js obx.eq([1, 2, 3], [3, 2, 1]) // -> false ``` Custom depth ```js obx.eq({ foo: "bar" }, { foo: "baz" }, { depth: 0 }) // -> true ``` ```js obx.eq({ foo: { bar: "baz" } }, { foo: {} }, { depth: 1 }) // -> true ``` ```js obx.eq({ foo: { bar: "baz" } }, { foo: {} }, { depth: 2 }) // -> false ``` Functions ```js // Unfortunately, functions are basically impossible to // diff. `eq` assumes that all functions are the same. obx.eq({ foo: (x) => x + 1 }, { foo: (x) => x + 2 }) // -> true ``` </details> ### `cp` Deep copy an object. **Params** - `o` : <code>Object</code> - *Object to copy* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of copy. Defaults to infinity* <details> <summary><b>Examples</b></summary> Copy by value, not by reference ```js const a = { foo: { bar: 'baz' } } const b = obx.cp(a) a.foo.bar = 'bar' console.log(b) // object remains the same // -> { // foo: { // bar: 'baz' // } // } ``` </details> ### `get` Get value from object. **Params** - `o` : <code>Object</code> - *The object* - `p` : <code>String</code> - *Value path* <details> <summary><b>Examples</b></summary> Get a deep key ```js obx.get("foo.bar", { foo: { bar: "baz" } }); // -> "baz" ``` Also works with arrays ```js obx.get("foo.2.baz", { foo: [ { foo: 'foo' }, { bar: 'bar' }, { baz: 'baz' } ] }); // -> "baz" ``` No key? No problem. ```js obx.get("foo.2.baz", { foo: 'bar' }) // -> null ``` </details> ### `set` Set value in object. **Params** - `o` : <code>Object</code> - *The object to be mutated* - `p` : <code>String</code> - *Value path* - `v` : <code>Object</code> - *Value to set* <details> <summary><b>Examples</b></summary> ```js const o = {} obx.set(o, "foo.bar.baz.haz", "hello") // -> { // foo: { // bar: { // baz: { // haz: "hello" // } // } // } // } ``` ```js const o = {} obx.set(o, "foo.2.foo", 'bar') // o -> { // foo: [<2 empty slots>, { // foo: 'bar' // }] // } ``` </details> ### `len` Recursively find the number of keys of an object. Note that this includes object-valued keys. **Params** - `o` : <code>Object</code> - *Object to find length of* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of len check. Defaults to infinity* <details> <summary><b>Examples</b></summary> Simple object ```js obx.len({ foo: 'bar', bar: 'baz' }) // -> 2 ``` Recursive object, depth 1 ```js // Here depth is only computed at the top level obx.len({ foo: 'bar', bar: { bar: 'baz', baz: [1, 2, 3] } }, { depth: 1 }) // -> 2 ``` Recursive object, infinite depth ```js // Note: array keys are counted obx.len({ foo: 'bar', bar: { bar: 'baz', baz: [1, 2, 3] } }) // -> 7 ``` </details> ### `map` Recursively map though all entries of an object. Note that map will also iterate through object-valued keys. **Params** - `o` : <code>Object</code> - *Object to map through* - `fn` : <code>function</code> - *Callback function. Contains [k, v] pair, path, object* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of map. Defaults to infinity* <details> <summary><b>Examples</b></summary> Basic Mapping ```js const o = { foo: "bar", bar: "baz", baz: "foo", }; // Note that map will callback on every value of the object, including sub objects! const emphasis = ([_, v]) => (v instanceof Object ? v : v + "!"); // Note that depth could be anything here, since this is just a flat object. obx.map(o, emphasis, { depth: 1 }); // -> { // foo: "bar!", // bar: "baz!", // baz: "foo!", // } ``` Recursive Mapping, low depth ```js const o = { foo: "bar", bar: { baz: "foo", }, }; // Note that map will callback on every value of the object, including sub objects! const emphasis = ([_, v]) => (v instanceof Object ? v : v + "!"); obx.map(o, emphasis, { depth: 1 }); // -> { // foo: "bar!", // bar: { // baz: "foo", // } // } // // Note that the inner key is unchanged. ``` Recursive Mapping, high depth ```js const o = { foo: "bar", bar: [ { foo: "bar", bar: "baz" }, { foo: "bar", bar: "baz" }, { foo: "bar", bar: "baz" }, ], raz: "faz", }; // Note that map will callback on every value of the object, including sub objects! const emphasis = ([_, v]) => (v instanceof Object ? v : v + "!"); obx.map(o, emphasis); // -> { // foo: "bar!", // bar: [ // { foo: "bar!", bar: "baz!" }, // { foo: "bar!", bar: "baz!" }, // { foo: "bar!", bar: "baz!" }, // ], // raz: "faz!", // } ``` </details> ### `reduce` Recursively reduce through all entries of an object. Note that reduce will also iterate through object-valued keys. **Params** - `o` : <code>Object</code> - *Object to map through* - `fn` : <code>function</code> - *Callback function* - `a` : <code>Object</code> - *Accumulator* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of reduce. Defaults to infinity* - `[.iter]` : <code>function</code> - *Iterator used by reduce. Defaults to inorder traversal.* <details> <summary><b>Examples</b></summary> Flat object ```js const o = { foo: "bar", bar: "baz" }; const combineVals = (a, [k, v]) => [...a, v]; obx.reduce(o, combineVals, []).join(", "); // -> "bar, baz" ``` Deeper object ```js const o = { foo: "bar", bar: { baz: "haz", }, }; const combineVals = (a, [k, v]) => (v instanceof Object ? a : [...a, v]); obx.reduce(o, combineVals, []).join(", "); // -> "bar, haz" ``` Custom depth ```js const o = { foo: "bar", bar: { baz: { haz: "wow", }, foo: "bar", }, raz: { faz: "maz", gaz: 'haz', haz: [ { maz: 'waz' }, { foo: 'moo' } ] }, } const combineVals = (a, [k, v]) => (v instanceof Object ? a : [...a, v]); obx.reduce(o, combineVals, [], { depth: 2 }).join(", "); // -> "bar, bar, maz, haz" // Only gets keys down to depth 2 ``` </details> ### `zip` Group multiple objects into a single iterator. Note that zip will also iterate through object-valued keys. **Params** - `objs` : <code>Array</code> - *Array of objects to be zipped together.* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of zip. Defaults to infinity* - `[.key]` : <code>Boolean</code> - *Whether zip should return object keys. Defaults to `false`* - `[.val]` : <code>Boolean</code> - *Whether zip should return object values. Defaults to `true`* - `[.last]` : <code>Boolean</code> - *Whether zip should stop iterating when the last object is done, as opposed to the first. Defaults to `false`* - `[.iter]` : <code>function</code> - *Iterator used by zip. Defaults to inorder traversal.* <details> <summary><b>Examples</b></summary> Stops at the first null value ```js const a = ["a", "b", "c"]; const b = [1]; // loop runs only once for (const z of obx.zip([a, b])) console.log(z) // -> ["a", 1] ``` Recursive ```js const a = { foo: "bar", bar: { baz: "haz", }, }; const b = [4, 5]; for (const z of obx.zip([a, b])) console.log(z) // -> ["bar", 4] // -> ["haz", 5] ``` More than 2 Objects ```js const a = ["a", "b", "c"]; const b = [1, 2, 3]; const c = ["x", "y", "z"]; const d = [3, 2, 1]; for (const z of obx.zip([a, b, c, d])) console.log(z) // -> ["a", 1, "x", 3] // -> ["b", 2, "y", 2] // -> ["c", 3, "z", 1] ``` </details> ### `sub` Recursive, in-place object subtraction. **Params** - `o` : <code>Object</code> - *The object to be subtracted from. This object is mutated.* - `s` : <code>Object</code> - *The object to subtract with* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of subtraction. Defaults to infinity* <details> <summary><b>Examples</b></summary> Simple subtraction ```js const a = { foo: "bar", bar: "baz", list: [1, 2, 3], }; const b = { foo: "bar", list: [1, 2, 3], }; obx.sub(a, b); console.log(a) // -> { bar: "baz" } ``` With arrays ```js const a = [1, 2, 3]; const b = [1, 2, 3]; obx.sub(a, b); console.log(a) // -> [] ``` </details> ### `add` Recursive, in-place object addition. If both objects contain the same key, defaults to o **Params** - `o` : <code>Object</code> - *The object to be added to. This object is mutated.* - `a` : <code>Object</code> - *The object to add with* - `params` : <code>Object</code> - *Parameters object* - `[.depth]` : <code>number</code> - *Depth of addition. Defaults to infinity* <details> <summary><b>Examples</b></summary> Simple addition ```js const a = { foo: "bar", bar: "baz", list: [1, 2, 3], }; const b = { foo: "bar", haz: 5, }; obx.add(a, b); console.log(a) // -> { foo: "bar", bar: "baz", list: [1, 2, 3], haz: 5 } ``` </details> ### `isEmptyObj` Assert that an object type is empty. **Params** - `o` : <code>Object</code> - *Object to assert is empty* <details> <summary><b>Examples</b></summary> ```js obx.isEmptyObj({}) // -> true ``` ```js obx.isEmptyObj({ foo: 'bar' }) // -> false ``` Only works for objects ```js obx.isEmptyObj([]) // -> false ``` </details> ### `isEmptyArr` Assert that an array type is empty. **Params** - `a` : <code>Array</code> - *The array to assert is empty* <details> <summary><b>Examples</b></summary> ```js obx.isEmptyArr([]) // -> true ``` ```js obx.isEmptyArr([1, 2, 3]) // -> false ``` Only works for arrays ```js obx.isEmptyArr({}) // -> false ``` </details> <p align="right">(<a href="#top">back to top</a>)</p> <!-- ROADMAP --> ## Roadmap - [x] Write docs - [ ] Write `eq` in terms of a list of objects? - [ ] Rewrite `eq` using inorder iterator - [x] Implement `zip` - [ ] Create new file of opinionated functions with nicer signatures based on existing functions - Other helpful functions like `isEmptyObj` and `isEmptyArr` could go in there too - [x] Add traversal options to - [x] `reduce` - [x] `zip` - [x] Complete test coverage - [x] Test `add` - [ ] Transition to TS <!-- See the [open issues](https://github.com/llGaetanll/obx/issues) for a full list of proposed features (and known issues). --> <p align="right">(<a href="#top">back to top</a>)</p> <!-- CONTRIBUTING --> <!-- ## Contributing Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again! 1. Fork the Project 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 4. Push to the Branch (`git push origin feature/AmazingFeature`) 5. Open a Pull Request <p align="right">(<a href="#top">back to top</a>)</p> --> <!-- LICENSE --> ## License Distributed under the MIT License. See `LICENSE.txt` for more information. <p align="right">(<a href="#top">back to top</a>)</p> <!-- CONTACT --> ## Contact **Twitter**: [@GaetanAlmela](https://twitter.com/GaetanAlmela) **Email**: npm@almela.io **GitHub Repo**: [llGaetanll/obx](https://github.com/llGaetanll/obx) <p align="right">(<a href="#top">back to top</a>)</p> <!-- MARKDOWN LINKS & IMAGES --> <!-- https://www.markdownguide.org/basic-syntax/#reference-style-links --> [contributors-shield]: https://img.shields.io/github/contributors/llGaetanll/obx.svg?style=for-the-badge [contributors-url]: https://github.com/llGaetanll/obx/graphs/contributors [forks-shield]: https://img.shields.io/github/forks/llGaetanll/obx.svg?style=for-the-badge [forks-url]: https://github.com/llGaetanll/obx/network/members [stars-shield]: https://img.shields.io/github/stars/llGaetanll/obx.svg?style=for-the-badge [stars-url]: https://github.com/llGaetanll/obx/stargazers [issues-shield]: https://img.shields.io/github/issues/llGaetanll/obx.svg?style=for-the-badge [issues-url]: https://github.com/llGaetanll/obx/issues [license-shield]: https://img.shields.io/github/license/llGaetanll/obx.svg?style=for-the-badge [license-url]: https://github.com/llGaetanll/obx/blob/master/LICENSE.txt [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 [linkedin-url]: https://linkedin.com/in/gaetan-almela-092973162