├── .github
├── FUNDING.yml
├── SPONSORS.yml
├── typescript-omit.png
├── typescript-pick.png
└── workflows
│ └── test.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── dist
├── index.d.ts
├── index.js
├── pathsAreEqual.d.ts
├── pathsAreEqual.js
├── recursiveOmit.d.ts
├── recursiveOmit.js
├── recursivePick.d.ts
└── recursivePick.js
├── eslint.config.js
├── package-lock.json
├── package.json
├── src
├── index.ts
├── pathsAreEqual.ts
├── recursiveOmit.ts
└── recursivePick.ts
├── test
├── index.test.ts
└── readme.test.ts
└── tsconfig.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: mesqueeb
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/SPONSORS.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: mesqueeb
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/typescript-omit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/filter-anything/1c4d48fca496a9a37070c39af6c1e239a26e6ff6/.github/typescript-omit.png
--------------------------------------------------------------------------------
/.github/typescript-pick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/filter-anything/1c4d48fca496a9a37070c39af6c1e239a26e6ff6/.github/typescript-pick.png
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | push:
4 | branches: main
5 | paths:
6 | - src/**
7 | - test/**
8 | - '*.js'
9 | - '*.ts'
10 | - '*.json'
11 | - .github/workflows/test.yml
12 | pull_request:
13 | branches: main
14 | paths:
15 | - src/**
16 | - test/**
17 | - '*.js'
18 | - '*.ts'
19 | - '*.json'
20 | - .github/workflows/test.yml
21 | concurrency:
22 | group: test-${{ github.ref }}
23 | cancel-in-progress: true
24 | jobs:
25 | test:
26 | strategy:
27 | matrix:
28 | node-version: ['18', '20']
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v4
32 | - uses: actions/setup-node@v4
33 | with:
34 | node-version: ${{ matrix.node-version }}
35 | cache: npm
36 | - run: npm ci
37 | - run: npm test
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .cache
3 | .env
4 | .env.js
5 | .rpt2_cache
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | import prettier from "@cycraft/eslint/prettier"
2 |
3 | export default prettier
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.defaultFormatter": "esbenp.prettier-vscode",
4 | "editor.tabSize": 2,
5 | "editor.insertSpaces": true,
6 | "files.insertFinalNewline": true,
7 | "files.trimFinalNewlines": true,
8 | "files.trimTrailingWhitespace": true,
9 | "javascript.preferences.importModuleSpecifierEnding": "js",
10 | "typescript.preferences.importModuleSpecifierEnding": "js"
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Luca Ban - Mesqueeb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Filter anything ⚔️
2 |
3 |
4 |
5 |
6 | ```
7 | npm i filter-anything
8 | ```
9 |
10 | An implementation that filters out object props like the TypeScript "pick" and "omit".
11 | In the Laravel world, this is also called "fillables" and "guard".
12 |
13 | ## Motivation
14 |
15 | I created this package because I needed:
16 |
17 | - be able to filter out object props based on just what we need - aka "pick" props
18 | - be able to filter out object props based on what we don't need - aka "omit" props
19 | - **supports for nested properties**
20 | - supports wildcards `*` for nested properties
21 | - the return type must be TypeScript supported! (see screenshots below)
22 |
23 | ## Meet the family (more tiny utils with TS support)
24 |
25 | - [is-what 🙉](https://github.com/mesqueeb/is-what)
26 | - [is-where 🙈](https://github.com/mesqueeb/is-where)
27 | - [merge-anything 🥡](https://github.com/mesqueeb/merge-anything)
28 | - [check-anything 👁](https://github.com/mesqueeb/check-anything)
29 | - [remove-anything ✂️](https://github.com/mesqueeb/remove-anything)
30 | - [getorset-anything 🐊](https://github.com/mesqueeb/getorset-anything)
31 | - [map-anything 🗺](https://github.com/mesqueeb/map-anything)
32 | - [filter-anything ⚔️](https://github.com/mesqueeb/filter-anything)
33 | - [copy-anything 🎭](https://github.com/mesqueeb/copy-anything)
34 | - [case-anything 🐫](https://github.com/mesqueeb/case-anything)
35 | - [flatten-anything 🏏](https://github.com/mesqueeb/flatten-anything)
36 | - [nestify-anything 🧅](https://github.com/mesqueeb/nestify-anything)
37 |
38 | ## Usage
39 |
40 | ### Pick
41 |
42 | With `pick` you pass an object and an array of keys of an object - **the props which may stay**.
43 |
44 | ```js
45 | import { pick } from 'filter-anything'
46 |
47 | const squirtle = { id: '007', name: 'Squirtle', type: 'water' }
48 |
49 | const newObject = pick(squirtle, ['name', 'type'])
50 | // returns { name: 'Squirtle', type: 'water' }
51 | ```
52 |
53 | ### Omit
54 |
55 | With `omit` you pass an object and an array of keys of an object - the props which should be removed.
56 |
57 | ```js
58 | import { omit } from 'filter-anything'
59 |
60 | const squirtle = { id: '007', name: 'Squirtle', type: 'water' }
61 |
62 | const withoutId = omit(squirtle, ['id'])
63 | // returns { name: 'Squirtle', type: 'water' }
64 | ```
65 |
66 | ### Aliases
67 |
68 | `pick()` and `omit()` can also be imported with the names `fillable()` and `guard()`. This pays homage to my history with Laravel. 😉
69 |
70 | ### TypeScript
71 |
72 | TypeScript users will love this, because, as you can see, the result has the correct type automatically!
73 |
74 | 
75 | 
76 |
77 | ### Nested props
78 |
79 | In the example below we want to get rid of the **nested property** called "discard".
80 |
81 | ```js
82 | const doc = { items: { keep: '📌', discard: '✂️' } }
83 |
84 | pick(doc, ['items.keep'])
85 | // returns {items: {keep: '📌'}}
86 |
87 | omit(doc, ['items.discard'])
88 | // returns {items: {keep: '📌'}}
89 | ```
90 |
91 | > Please note that TypeScript users will need to cast the result when using nested props.
92 |
93 | ## Wildcards
94 |
95 | Yes! You can also work with wildcards by using `*` in the path.
96 |
97 | ```js
98 | const doc = {
99 | 123: { keep: '📌', discard: '✂️' },
100 | 456: { keep: '📌', discard: '✂️' },
101 | }
102 | // use wildcard *
103 | omit(doc, ['*.discard'])
104 | // returns {
105 | // '123': {keep: '📌'},
106 | // '456': {keep: '📌'}
107 | // }
108 | ```
109 |
110 | > Please note that TypeScript users will need to cast the result when using wildcards props.
111 |
112 | Feel free to open issues for any requests, questions or bugs!
113 |
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import { F, O, S, U } from 'ts-toolbelt';
2 | /**
3 | * pick returns a new object with only the props you pick
4 | *
5 | * @export
6 | * @template T
7 | * @template K
8 | * @param {T} obj the target object to pick props from
9 | * @param {K[]} keys an array of prop names you want to keep - allows dot-notation for nested props, eg. `nested.prop` will keep just `{ nested: { prop: 1 } }`
10 | * @returns {O.Pick} a new object with just the picked props
11 | */
12 | export declare function pick(obj: T, keys: F.AutoPath[]): U.Merge>>;
15 | export declare const fillable: typeof pick;
16 | /**
17 | * omit returns a new object without the props you omit
18 | *
19 | * @export
20 | * @template T
21 | * @template K
22 | * @param {T} obj the target object to omit props from
23 | * @param {K[]} keys the prop names you want to omit
24 | * @returns {O.Omit} a new object without the omitted props
25 | */
26 | export declare function omit(obj: T, keys: F.AutoPath[]): U.Merge>>;
29 | export declare const guard: typeof omit;
30 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | import { isFullArray } from 'is-what';
2 | import { recursiveOmit } from './recursiveOmit.js';
3 | import { recursivePick } from './recursivePick.js';
4 | /**
5 | * pick returns a new object with only the props you pick
6 | *
7 | * @export
8 | * @template T
9 | * @template K
10 | * @param {T} obj the target object to pick props from
11 | * @param {K[]} keys an array of prop names you want to keep - allows dot-notation for nested props, eg. `nested.prop` will keep just `{ nested: { prop: 1 } }`
12 | * @returns {O.Pick} a new object with just the picked props
13 | */
14 | export function pick(obj, keys) {
15 | if (!isFullArray(keys))
16 | return {};
17 | return recursivePick(obj, keys);
18 | }
19 | export const fillable = pick;
20 | /**
21 | * omit returns a new object without the props you omit
22 | *
23 | * @export
24 | * @template T
25 | * @template K
26 | * @param {T} obj the target object to omit props from
27 | * @param {K[]} keys the prop names you want to omit
28 | * @returns {O.Omit} a new object without the omitted props
29 | */
30 | export function omit(obj, keys) {
31 | if (!isFullArray(keys))
32 | return obj;
33 | return recursiveOmit(obj, keys);
34 | }
35 | export const guard = omit;
36 |
--------------------------------------------------------------------------------
/dist/pathsAreEqual.d.ts:
--------------------------------------------------------------------------------
1 | export declare function pathsAreEqual(path: string, wildcardPath: string): boolean;
2 |
--------------------------------------------------------------------------------
/dist/pathsAreEqual.js:
--------------------------------------------------------------------------------
1 | export function pathsAreEqual(path, wildcardPath) {
2 | const wildcardPathPieces = wildcardPath.split('.');
3 | const pathWithWildcards = path
4 | .split('.')
5 | .reduce((carry, piece, index) => {
6 | const add = wildcardPathPieces[index] === '*' ? '*' : piece;
7 | carry.push(add);
8 | return carry;
9 | }, [])
10 | .join('.');
11 | return pathWithWildcards === wildcardPath;
12 | }
13 |
--------------------------------------------------------------------------------
/dist/recursiveOmit.d.ts:
--------------------------------------------------------------------------------
1 | export declare function recursiveOmit(obj: T, omittedKeys: OmittedKeys, pathUntilNow?: string): T;
4 |
--------------------------------------------------------------------------------
/dist/recursiveOmit.js:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from 'is-what';
2 | import { pathsAreEqual } from './pathsAreEqual.js';
3 | export function recursiveOmit(obj, omittedKeys, pathUntilNow = '') {
4 | if (!isPlainObject(obj)) {
5 | return obj;
6 | }
7 | return Object.entries(obj).reduce((carry, [key, value]) => {
8 | let path = pathUntilNow;
9 | if (path)
10 | path += '.';
11 | path += key;
12 | if (omittedKeys.some((guardPath) => pathsAreEqual(path, guardPath))) {
13 | return carry;
14 | }
15 | // no further recursion needed
16 | if (!isPlainObject(value)) {
17 | carry[key] = value;
18 | return carry;
19 | }
20 | carry[key] = recursiveOmit(obj[key], omittedKeys, path);
21 | return carry;
22 | }, {});
23 | }
24 |
--------------------------------------------------------------------------------
/dist/recursivePick.d.ts:
--------------------------------------------------------------------------------
1 | export declare function recursivePick(obj: T, pickedKeys: PickedKeys, pathUntilNow?: string): T;
4 |
--------------------------------------------------------------------------------
/dist/recursivePick.js:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from 'is-what';
2 | import { pathsAreEqual } from './pathsAreEqual.js';
3 | export function recursivePick(obj, pickedKeys, pathUntilNow = '') {
4 | if (!isPlainObject(obj)) {
5 | return obj;
6 | }
7 | return Object.entries(obj).reduce((carry, [key, value]) => {
8 | let path = pathUntilNow;
9 | if (path)
10 | path += '.';
11 | path += key;
12 | // check pickedKeys up to this point
13 | if (pickedKeys.length) {
14 | let passed = false;
15 | pickedKeys.forEach((pickedKey) => {
16 | const pathDepth = path.split('.').length;
17 | const pickedKeyDepth = pickedKey.split('.').length;
18 | const pickedKeyUpToNow = pickedKey.split('.').slice(0, pathDepth).join('.');
19 | const pathUpToPickedKeyDepth = path.split('.').slice(0, pickedKeyDepth).join('.');
20 | if (pathsAreEqual(pathUpToPickedKeyDepth, pickedKeyUpToNow))
21 | passed = true;
22 | });
23 | // there's not one pickedKey that allows up to now
24 | if (!passed)
25 | return carry;
26 | }
27 | // no further recursion needed
28 | if (!isPlainObject(value)) {
29 | carry[key] = value;
30 | return carry;
31 | }
32 | carry[key] = recursivePick(obj[key], pickedKeys, path);
33 | return carry;
34 | }, {});
35 | }
36 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import config from '@cycraft/eslint/config'
2 |
3 | export default [
4 | ...config,
5 | {
6 | rules: {
7 | '@typescript-eslint/no-explicit-any': 'warn',
8 | },
9 | },
10 | ]
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "filter-anything",
3 | "version": "4.0.3",
4 | "description": "A simple (TypeScript) integration of \"pick\" and \"omit\" to filter props of an object",
5 | "type": "module",
6 | "sideEffects": false,
7 | "exports": {
8 | ".": "./dist/index.js"
9 | },
10 | "engines": {
11 | "node": ">=18"
12 | },
13 | "scripts": {
14 | "lint": "tsc --noEmit && eslint ./src",
15 | "test": "vitest run",
16 | "build": "del-cli dist && tsc",
17 | "release": "npm run lint && npm run build && np"
18 | },
19 | "dependencies": {
20 | "is-what": "^5.2.0",
21 | "ts-toolbelt": "^9.6.0"
22 | },
23 | "devDependencies": {
24 | "@cycraft/eslint": "^0.4.3",
25 | "@cycraft/tsconfig": "^0.1.2",
26 | "del-cli": "^6.0.0",
27 | "np": "^10.2.0",
28 | "vitest": "^3.0.6"
29 | },
30 | "files": [
31 | "dist"
32 | ],
33 | "keywords": [
34 | "pick",
35 | "omit",
36 | "picked-props",
37 | "omited-props",
38 | "typescript",
39 | "fillables",
40 | "guard",
41 | "filter",
42 | "wildcard",
43 | "props-stay",
44 | "filter-props",
45 | "delete-props",
46 | "guarded",
47 | "guarded-props",
48 | "filter-fields"
49 | ],
50 | "author": "Luca Ban - Mesqueeb (https://cycraft.co)",
51 | "funding": "https://github.com/sponsors/mesqueeb",
52 | "license": "MIT",
53 | "repository": {
54 | "type": "git",
55 | "url": "https://github.com/mesqueeb/filter-anything.git"
56 | },
57 | "homepage": "https://github.com/mesqueeb/filter-anything#readme",
58 | "bugs": "https://github.com/mesqueeb/filter-anything/issues"
59 | }
60 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { isFullArray } from 'is-what'
2 | import { F, O, S, U } from 'ts-toolbelt'
3 | import { recursiveOmit } from './recursiveOmit.js'
4 | import { recursivePick } from './recursivePick.js'
5 |
6 | /**
7 | * pick returns a new object with only the props you pick
8 | *
9 | * @export
10 | * @template T
11 | * @template K
12 | * @param {T} obj the target object to pick props from
13 | * @param {K[]} keys an array of prop names you want to keep - allows dot-notation for nested props, eg. `nested.prop` will keep just `{ nested: { prop: 1 } }`
14 | * @returns {O.Pick} a new object with just the picked props
15 | */
16 | export function pick(
17 | obj: T,
18 | keys: F.AutoPath[],
19 | ): U.Merge>> {
20 | if (!isFullArray(keys)) return {} as any
21 |
22 | return recursivePick(obj, keys) as any
23 | }
24 |
25 | export const fillable = pick
26 |
27 | /**
28 | * omit returns a new object without the props you omit
29 | *
30 | * @export
31 | * @template T
32 | * @template K
33 | * @param {T} obj the target object to omit props from
34 | * @param {K[]} keys the prop names you want to omit
35 | * @returns {O.Omit} a new object without the omitted props
36 | */
37 | export function omit(
38 | obj: T,
39 | keys: F.AutoPath[],
40 | ): U.Merge>> {
41 | if (!isFullArray(keys)) return obj as any
42 |
43 | return recursiveOmit(obj, keys) as any
44 | }
45 |
46 | export const guard = omit
47 |
--------------------------------------------------------------------------------
/src/pathsAreEqual.ts:
--------------------------------------------------------------------------------
1 | export function pathsAreEqual(path: string, wildcardPath: string): boolean {
2 | const wildcardPathPieces = wildcardPath.split('.')
3 | const pathWithWildcards = path
4 | .split('.')
5 | .reduce((carry, piece, index) => {
6 | const add = wildcardPathPieces[index] === '*' ? '*' : piece
7 | carry.push(add)
8 | return carry
9 | }, [])
10 | .join('.')
11 | return pathWithWildcards === wildcardPath
12 | }
13 |
--------------------------------------------------------------------------------
/src/recursiveOmit.ts:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from 'is-what'
2 | import { pathsAreEqual } from './pathsAreEqual.js'
3 |
4 | export function recursiveOmit(
5 | obj: T,
6 | omittedKeys: OmittedKeys,
7 | pathUntilNow = '',
8 | ): T {
9 | if (!isPlainObject(obj)) {
10 | return obj
11 | }
12 | return Object.entries(obj).reduce<{ [key in string]: unknown }>((carry, [key, value]) => {
13 | let path = pathUntilNow
14 | if (path) path += '.'
15 | path += key
16 | if (omittedKeys.some((guardPath) => pathsAreEqual(path, guardPath))) {
17 | return carry
18 | }
19 | // no further recursion needed
20 | if (!isPlainObject(value)) {
21 | carry[key] = value
22 | return carry
23 | }
24 | carry[key] = recursiveOmit(obj[key] as any, omittedKeys, path)
25 | return carry
26 | }, {}) as T
27 | }
28 |
--------------------------------------------------------------------------------
/src/recursivePick.ts:
--------------------------------------------------------------------------------
1 | import { isPlainObject } from 'is-what'
2 | import { pathsAreEqual } from './pathsAreEqual.js'
3 |
4 | export function recursivePick(
5 | obj: T,
6 | pickedKeys: PickedKeys,
7 | pathUntilNow = '',
8 | ): T {
9 | if (!isPlainObject(obj)) {
10 | return obj
11 | }
12 | return Object.entries(obj).reduce<{ [key in string]: unknown }>((carry, [key, value]) => {
13 | let path = pathUntilNow
14 | if (path) path += '.'
15 | path += key
16 | // check pickedKeys up to this point
17 | if (pickedKeys.length) {
18 | let passed = false
19 | pickedKeys.forEach((pickedKey) => {
20 | const pathDepth = path.split('.').length
21 | const pickedKeyDepth = pickedKey.split('.').length
22 | const pickedKeyUpToNow = pickedKey.split('.').slice(0, pathDepth).join('.')
23 | const pathUpToPickedKeyDepth = path.split('.').slice(0, pickedKeyDepth).join('.')
24 | if (pathsAreEqual(pathUpToPickedKeyDepth, pickedKeyUpToNow)) passed = true
25 | })
26 | // there's not one pickedKey that allows up to now
27 | if (!passed) return carry
28 | }
29 | // no further recursion needed
30 | if (!isPlainObject(value)) {
31 | carry[key] = value
32 | return carry
33 | }
34 | carry[key] = recursivePick(obj[key] as any, pickedKeys, path)
35 | return carry
36 | }, {}) as T
37 | }
38 |
--------------------------------------------------------------------------------
/test/index.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { omit, pick } from '../src/index.js'
3 | import { pathsAreEqual } from '../src/pathsAreEqual.js'
4 |
5 | test('check pick FLAT', () => {
6 | const doc = { name: 'n1', id: '1', filled: true, notfilled: false }
7 | const res = pick(doc, ['name', 'filled', 'id'])
8 | expect(res).toEqual({ name: 'n1', id: '1', filled: true })
9 | })
10 |
11 | test('check pick 0 props', () => {
12 | const doc = { name: 'n1', id: '1', filled: true, notfilled: false }
13 | const res = pick(doc, [] as any)
14 | expect(res).toEqual({} as any)
15 | })
16 |
17 | test('check omit FLAT', () => {
18 | const doc = { name: 'n1', id: '1', filled: true, omited: true }
19 | const res = omit(doc, ['omited'])
20 | expect(res).toEqual({ name: 'n1', id: '1', filled: true })
21 | })
22 |
23 | test('check picks NESTED - single pick', () => {
24 | const doc = {
25 | nested: { picks: { yes: 0, no: 0 } },
26 | secondProp: true,
27 | }
28 | const res = pick(doc, ['nested.picks.yes'])
29 | expect(res).toEqual({ nested: { picks: { yes: 0 } } })
30 | })
31 |
32 | test('check picks NESTED - single pick 2', () => {
33 | const doc = {
34 | nested: { picks: { yes: 0, no: 0 } },
35 | secondProp: true,
36 | }
37 | const res = pick(doc, ['nested.picks.yes'])
38 | expect(res).toEqual({ nested: { picks: { yes: 0 } } })
39 | })
40 |
41 | test('check picks NESTED - single pick 3', () => {
42 | const doc = {
43 | nested: { picks: { yes: 0, no: 0 } },
44 | secondProp: true,
45 | }
46 | const res = pick(doc, ['nested'])
47 | expect(res).toEqual({ nested: { picks: { yes: 0, no: 0 } } })
48 | })
49 |
50 | test('check picks NESTED - multiple pick', () => {
51 | const doc = {
52 | nested: { picks: { yes: 0, no: 0 } },
53 | secondProp: { yes: true, no: false },
54 | }
55 |
56 | const res1 = pick(doc, ['nested.picks.yes', 'secondProp.yes'])
57 | expect(res1).toEqual({ nested: { picks: { yes: 0 } }, secondProp: { yes: true } })
58 |
59 | const res2 = pick(doc, ['nested.picks', 'secondProp.yes'])
60 | expect(res2).toEqual({ nested: { picks: { yes: 0, no: 0 } }, secondProp: { yes: true } })
61 |
62 | const res3 = pick(doc, ['nested', 'secondProp.yes'])
63 | expect(res3).toEqual({ nested: { picks: { yes: 0, no: 0 } }, secondProp: { yes: true } })
64 | })
65 |
66 | test('check omit NESTED', () => {
67 | const doc = {
68 | nested: { omit: { yes: 0, no: 0 } },
69 | secondProp: true,
70 | }
71 |
72 | const res1 = omit(doc, ['nested.omit.yes'])
73 | expect(res1).toEqual({ nested: { omit: { no: 0 } }, secondProp: true })
74 |
75 | const res2 = omit(doc, ['nested.omit'])
76 | expect(res2).toEqual({ nested: {}, secondProp: true })
77 |
78 | const res3 = omit(doc, ['nested'])
79 | expect(res3).toEqual({ secondProp: true })
80 |
81 | const res4 = omit(doc, ['nested.omit.yes', 'secondProp'])
82 | expect(res4).toEqual({ nested: { omit: { no: 0 } } } as any)
83 | })
84 |
85 | test('check NESTED wildcards - pick', () => {
86 | const doc = {
87 | picks: {
88 | '123': { yes: true, no: false },
89 | '456': { yes: true, no: false },
90 | },
91 | omited: {
92 | '123': { yes: true, no: false },
93 | },
94 | }
95 |
96 | type Res = { picks: { '123': { yes: boolean }; '456': { yes: boolean } } }
97 |
98 | const res: Res = pick(doc, ['picks.*.yes'] as any)
99 |
100 | expect(res).toEqual({
101 | picks: {
102 | '123': { yes: true },
103 | '456': { yes: true },
104 | },
105 | })
106 | })
107 | test('check NESTED wildcards - omit', () => {
108 | const doc = {
109 | picks: { '456': { yes: true, no: false } },
110 | omited: { '123': { yes: true, no: false }, '456': { yes: true, no: false } },
111 | }
112 | const res = omit(doc, ['omited.*.yes'] as any)
113 | expect(res).toEqual({
114 | picks: { '456': { yes: true, no: false } },
115 | omited: { '123': { no: false }, '456': { no: false } },
116 | })
117 | })
118 |
119 | test('pathsAreEqual', () => {
120 | expect(pathsAreEqual('bob', '*')).toEqual(true)
121 | expect(pathsAreEqual('bob.and.alice', 'bob.*.alice')).toEqual(true)
122 | })
123 |
--------------------------------------------------------------------------------
/test/readme.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { omit, pick } from '../src/index.js'
3 |
4 | test('1', () => {
5 | const squirtle = { id: '007', name: 'Squirtle', type: 'water' }
6 |
7 | const withoutId = pick(squirtle, ['name', 'type'])
8 |
9 | expect(withoutId).toEqual({ name: 'Squirtle', type: 'water' })
10 | })
11 |
12 | test('2', () => {
13 | const squirtle = { id: '007', name: 'Squirtle', type: 'water' }
14 |
15 | const withoutId = omit(squirtle, ['id'])
16 |
17 | expect(withoutId).toEqual({ name: 'Squirtle', type: 'water' })
18 | })
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@cycraft/tsconfig",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "outDir": "dist"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------