diff --git a/packages/tools/README.md b/packages/tools/README.md index 8e6d741..bad5589 100644 --- a/packages/tools/README.md +++ b/packages/tools/README.md @@ -125,6 +125,30 @@ import { atob } from "@timberio/tools"; console.log(atob("hello world")); // <-- returns "aGVsbG8gd29ybGQ=" ``` +### `pluckMultiple(source: { [key: string]: any }, paths: string[])` + +Returns a new object with just those paths which is requested from source object. + +** Usage example ** + +```typescript +import { pluckMultiple } from '@timberio/tools'; +const paths = ["length", "response.status"]; +const source = { + request: { + secure: true, + headers: { "content-type": "text/plain" }, + href: "http://google.com/?q=whatever", + }, + response: { status: 200 }, + length: 40, + message: null, +}; + +const result = pluckMultiple(source, paths); +console.log(result); // { "length": 40, "response": { status: 200 }} +``` + ### LICENSE [ISC](LICENSE.md) diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index e7fe153..796d8a2 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -3,6 +3,7 @@ import Queue from "./queue"; import { base64Encode } from "./encode"; import makeBatch from "./batch"; import makeThrottle from "./throttle"; +import pluckMultiple from "./pluck-multiple"; export { // Types @@ -12,5 +13,6 @@ export { // Functions base64Encode, makeBatch, - makeThrottle + makeThrottle, + pluckMultiple, }; diff --git a/packages/tools/src/pluck-multiple.test.ts b/packages/tools/src/pluck-multiple.test.ts new file mode 100644 index 0000000..467dbec --- /dev/null +++ b/packages/tools/src/pluck-multiple.test.ts @@ -0,0 +1,119 @@ +import pluckMultiple from "./pluck-multiple"; + +describe("pluck multiple tests", () => { + it("should return an obj with given paths", () => { + const paths = ["length"]; + const source = { + length: 40, + message: null, + }; + + const expected = { length: 40 }; + expect(pluckMultiple(source, paths)).toEqual(expected); + }); + + it("should return an obj with given paths when nested ctx is given", () => { + const paths = ["length", "request.headers", "response.status"]; + const source = { + request: { + secure: true, + headers: { "content-type": "text/plain" }, + href: "http://google.com/?q=whatever", + }, + response: { status: 200 }, + length: 40, + message: null, + }; + + const expected = { + request: { + headers: { "content-type": "text/plain" }, + }, + length: 40, + response: { status: 200 }, + }; + expect(pluckMultiple(source, paths)).toEqual(expected); + }); + + it("should set undefined in destination if source does not have the path requested", () => { + const paths = ["length", "request.headers", "response.status", "foo.bar"]; + const source = { + request: { + secure: true, + headers: { "content-type": "text/plain" }, + href: "http://google.com/?q=whatever", + }, + response: { status: 200 }, + length: 40, + message: null, + }; + + const expected = { + request: { + headers: { "content-type": "text/plain" }, + }, + length: 40, + response: { status: 200 }, + foo: { + bar: undefined, + }, + }; + expect(pluckMultiple(source, paths)).toEqual(expected); + }); + + it("shoudl work with path with same root like request.headers request.href", () => { + const paths = [ + "length", + "request.headers", + "request.href", + "request.body.text", + "request.body.json.username", + "request.body.json.address.pin", + "response.status", + "foo.bar", + ]; + + const source = { + request: { + secure: true, + headers: { "content-type": "text/plain" }, + href: "http://google.com/?q=whatever", + body: { + json: { + username: "user123", + address: { + street: "str str", + pin: 300011, + }, + }, + text: "", + }, + }, + response: { status: 200 }, + length: 40, + message: null, + }; + + const expected = { + request: { + headers: { "content-type": "text/plain" }, + href: "http://google.com/?q=whatever", + body: { + json: { + username: "user123", + address: { + pin: 300011, + }, + }, + text: "", + }, + }, + length: 40, + response: { status: 200 }, + foo: { + bar: undefined, + }, + }; + expect(pluckMultiple(source, paths)).toEqual(expected); + }); +}); diff --git a/packages/tools/src/pluck-multiple.ts b/packages/tools/src/pluck-multiple.ts new file mode 100644 index 0000000..e9c5def --- /dev/null +++ b/packages/tools/src/pluck-multiple.ts @@ -0,0 +1,70 @@ +/** + * gets the value from source for given path + * + * @param source - object + * @param path - string + */ +function get(source: object | any, path: string) { + let segs: string[] = path.split("."); + + while (segs.length) { + let seg: string | undefined = segs.shift(); + if (seg) { + if (source[seg] === undefined) return undefined; + source = source[seg]; + } + } + return source; +} + +/** + * sets the value for given path in the destination object + * + * @param destination - object + * @param path - string + * @param value - any + */ +function set(destination: { [key: string]: any }, path: string, value: any) { + const segs: string[] = path.split("."); + + let seg: string | undefined = segs.shift(); + let next: { [key: string]: any } = {}; + + if (seg) { + if (destination[seg]) { + next = destination[seg]; + } else { + next = destination[seg] = !segs.length ? value : {}; + } + } + + while (segs.length) { + seg = segs.shift(); + if (seg) { + if (!segs.length) { + next[seg] = value; + } else if (!next[seg]) { + next[seg] = {}; + } + next = next[seg]; + } + } +} + +/** + * lets us request a 'path' of deeply nested object keys, + * and returns just those keys in a new object + * + * @param source - object + * @param paths - string[] + */ +export default function pluckMultiple( + source: { [key: string]: any }, + paths: string[] +) { + const destination = {}; + for (const path of paths) { + set(destination, path, get(source, path)); + } + return destination; +}