Merge pull request #108 from ajaishankar/feature/object-predicate

filter shortcut for scalar properties
This commit is contained in:
Anoop M D 2023-02-27 21:32:59 +05:30 committed by GitHub
commit e6a754b933
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 11 deletions

View File

@ -18,6 +18,10 @@ Array filtering [?] with corresponding filter function
```js ```js
get(data, '..items[?].amount', i => i.amount > 20) get(data, '..items[?].amount', i => i.amount > 20)
``` ```
Array filtering [?] with simple object predicate, same as (i => i.id === 2 && i.amount === 20)
```js
get(data, '..items[?]', { id: 2, amount: 20 })
```
Array mapping [?] with corresponding mapper function Array mapping [?] with corresponding mapper function
```js ```js
get(data, '..items[?].amount', i => i.amount + 10) get(data, '..items[?].amount', i => i.amount + 10)
@ -26,4 +30,4 @@ get(data, '..items[?].amount', i => i.amount + 10)
### Publish to Npm Registry ### Publish to Npm Registry
```bash ```bash
npm publish --access=public npm publish --access=public
``` ```

View File

@ -19,11 +19,11 @@ function normalize(value: any) {
/** /**
* Gets value of a prop from source. * Gets value of a prop from source.
* *
* If source is an array get value for each item. * If source is an array get value from each item.
* *
* If deep is true then recursively gets values for prop in nested objects. * If deep is true then recursively gets values for prop in nested objects.
* *
* Once a value if found will not recurese further into that value. * Once a value is found will not recurse further into that value.
*/ */
function getValue(source: any, prop: string, deep = false): any { function getValue(source: any, prop: string, deep = false): any {
if (typeof source !== 'object') return; if (typeof source !== 'object') return;
@ -47,14 +47,27 @@ function getValue(source: any, prop: string, deep = false): any {
return normalize(value); return normalize(value);
} }
type PredicateOrMapper = (obj: any) => any; type PredicateOrMapper = ((obj: any) => any) | Record<string, any>;
/**
* Make a predicate function that checks scalar properties for equality
*/
function objectPredicate(obj: Record<string, any>) {
return (item: any) => {
for (const [key, value] of Object.entries(obj)) {
if (item[key] !== value) return false;
}
return true;
};
}
/** /**
* Apply filter on source array or object * Apply filter on source array or object
* *
* If the filter returns a non boolean non null value it is treated as a mapped value * If the filter returns a non boolean non null value it is treated as a mapped value
*/ */
function filterOrMap(source: any, fun: PredicateOrMapper) { function filterOrMap(source: any, funOrObj: PredicateOrMapper) {
const fun = typeof funOrObj === 'object' ? objectPredicate(funOrObj) : funOrObj;
const isArray = Array.isArray(source); const isArray = Array.isArray(source);
const list = isArray ? source : [source]; const list = isArray ? source : [source];
const result = [] as any[]; const result = [] as any[];
@ -67,7 +80,7 @@ function filterOrMap(source: any, fun: PredicateOrMapper) {
result.push(value); // mapper result.push(value); // mapper
} }
} }
return isArray ? result : result[0]; return normalize(isArray ? result : result[0]);
} }
/** /**
@ -89,7 +102,11 @@ function filterOrMap(source: any, fun: PredicateOrMapper) {
* ```js * ```js
* get(data, '..items[?].amount', i => i.amount > 20) * get(data, '..items[?].amount', i => i.amount > 20)
* ``` * ```
* 5. Array mapping [?] with corresponding mapper function * 5. Array filtering [?] with simple object predicate, same as (i => i.id === 2 && i.amount === 20)
* ```js
* get(data, '..items[?]', { id: 2, amount: 20 })
* ```
* 6. Array mapping [?] with corresponding mapper function
* ```js * ```js
* get(data, '..items[?].amount', i => i.amount + 10) * get(data, '..items[?].amount', i => i.amount + 10)
* ``` * ```
@ -121,7 +138,7 @@ export function get(source: any, path: string, ...fns: PredicateOrMapper[]) {
source = filterOrMap(source, fun); source = filterOrMap(source, fun);
break; break;
case typeof token === 'number': case typeof token === 'number':
source = source[token]; source = normalize(source[token]);
break; break;
default: default:
source = getValue(source, token as string, lookbehind === ".."); source = getValue(source, token as string, lookbehind === "..");
@ -131,4 +148,4 @@ export function get(source: any, path: string, ...fns: PredicateOrMapper[]) {
} }
return source; return source;
} }

View File

@ -22,7 +22,7 @@ const data = {
{ id: 4, amount: 40 } { id: 4, amount: 40 }
] ]
} }
] ],
}, },
}; };
@ -48,11 +48,13 @@ describe("get", () => {
// filter and map // filter and map
it.each([ it.each([
["..items[?].amount", [40], (i: any) => i.amount > 30], // [?] filter ["..items[?].amount", [40], (i: any) => i.amount > 30], // [?] filter
["..items[?].amount", [40], { id: 4, amount: 40 }], // object filter
["..items[?].amount", undefined, { id: 5, amount: 40 }],
["..items..amount[?][0]", 40, (amt: number) => amt > 30], ["..items..amount[?][0]", 40, (amt: number) => amt > 30],
["..items..amount[0][?]", undefined, (amt: number) => amt > 30], // filter on single value ["..items..amount[0][?]", undefined, (amt: number) => amt > 30], // filter on single value
["..items..amount[?]", [11, 21, 31, 41], (amt: number) => amt + 1], // [?] mapper ["..items..amount[?]", [11, 21, 31, 41], (amt: number) => amt + 1], // [?] mapper
["..items..amount[0][?]", 11, (amt: number) => amt + 1], // [?] map on single value ["..items..amount[0][?]", 11, (amt: number) => amt + 1], // [?] map on single value
])("%s should be %j %s", (expr, result, filter) => { ])("%s should be %j for %s", (expr, result, filter) => {
expect(get(data, expr, filter)).toEqual(result); expect(get(data, expr, filter)).toEqual(result);
}); });