-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add dot notation path setter and getter (#108)
Because - Formik use this kind of path notation to set proper object, we need this kind of function too. - `dot.setter(obj, "path.to.value", "hi")` will construct `{ path: { to: { value: "hi" }}}` This commit - Implement dot.setter and dot.getter
- Loading branch information
Showing
4 changed files
with
209 additions
and
6 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Context | ||
|
||
This lib convert dot notation like `path.to.value` to reference `{ path: { to: value }}` | ||
|
||
# Reference | ||
|
||
- [Formik - setIn](https://github.com/jaredpalmer/formik/blob/b9cc2536a1edb9f2d69c4cd20ecf4fa0f8059ade/packages/formik/src/utils.ts#L106) | ||
- [Formil - getIn](https://github.com/jaredpalmer/formik/blob/b9cc2536a1edb9f2d69c4cd20ecf4fa0f8059ade/packages/formik/src/utils.ts#L69) | ||
- [Convert a JavaScript string in dot notation into an object reference](https://stackoverflow.com/questions/6393943/convert-a-javascript-string-in-dot-notation-into-an-object-reference) | ||
- [Lodash - BaseSet](https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js#L3965) | ||
|
||
# Caveats | ||
|
||
- Currently don't support bracket path `foo[0][1]`, it only support `foo.0.1` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import dot from "."; | ||
|
||
describe("getter", () => { | ||
const obj = { | ||
foo: { | ||
bar: "yes!", | ||
}, | ||
}; | ||
|
||
it("gets a value by array path", () => { | ||
expect(dot.getter(obj, ["foo", "bar"])).toBe("yes!"); | ||
}); | ||
|
||
it("gets a value by string path", () => { | ||
expect(dot.getter(obj, "foo.bar")).toBe("yes!"); | ||
}); | ||
|
||
it('return "undefined" if value was not found using given path', () => { | ||
expect(dot.getter(obj, "foo.aar")).toBeUndefined(); | ||
}); | ||
|
||
it("return defaultValue if value was not found using given path", () => { | ||
expect(dot.getter(obj, "foo.aar", "no!")).toBe("no!"); | ||
}); | ||
}); | ||
|
||
describe("setter", () => { | ||
it("sets flat value", () => { | ||
const obj = { foo: "bar" }; | ||
dot.setter(obj, "flat", "value"); | ||
expect(obj).toEqual({ foo: "bar", flat: "value" }); | ||
}); | ||
|
||
it("removes flat value", () => { | ||
const obj = { foo: "bar" }; | ||
dot.setter(obj, "foo", undefined); | ||
expect(obj).toEqual({}); | ||
}); | ||
|
||
it("sets nested value", () => { | ||
const obj = { x: "y" }; | ||
dot.setter(obj, "foo.bar", "hi"); | ||
expect(obj).toEqual({ x: "y", foo: { bar: "hi" } }); | ||
}); | ||
|
||
it("updates nested value", () => { | ||
const obj = { x: "y", foo: { bar: "a" } }; | ||
dot.setter(obj, "foo.bar", "b"); | ||
expect(obj).toEqual({ x: "y", foo: { bar: "b" } }); | ||
}); | ||
|
||
it("removes nested value", () => { | ||
const obj = { x: "y", foo: { bar: "a" } }; | ||
dot.setter(obj, "foo.bar", undefined); | ||
expect(obj).toEqual({ x: "y", foo: {} }); | ||
expect(obj.foo).not.toHaveProperty("bar"); | ||
}); | ||
|
||
it("updates deep nested value", () => { | ||
const obj = { x: "y", twofoldly: { foo: { bar: "a" } } }; | ||
dot.setter(obj, "twofoldly.foo.bar", "b"); | ||
expect(obj).toEqual({ x: "y", twofoldly: { foo: { bar: "b" } } }); | ||
}); | ||
|
||
it("removes deep nested value", () => { | ||
const obj = { x: "y", twofoldly: { foo: { bar: "a" } } }; | ||
dot.setter(obj, "twofoldly.foo.bar", undefined); | ||
expect(obj).toEqual({ x: "y", twofoldly: { foo: {} } }); | ||
expect(obj.twofoldly.foo).not.toHaveProperty("bar"); | ||
}); | ||
|
||
it("sets new array", () => { | ||
const obj = { x: "y" }; | ||
dot.setter(obj, "foo.0", "bar"); | ||
expect(obj).toEqual({ x: "y", foo: ["bar"] }); | ||
}); | ||
|
||
it("updates nested array value", () => { | ||
const obj = { x: "y", foo: ["bar"] }; | ||
dot.setter(obj, "foo.0", "bar"); | ||
expect(obj).toEqual({ x: "y", foo: ["bar"] }); | ||
}); | ||
|
||
it("adds new item to nested array", () => { | ||
const obj = { x: "y", foo: ["bar"] }; | ||
dot.setter(obj, "foo.1", "bar2"); | ||
expect(obj).toEqual({ x: "y", foo: ["bar", "bar2"] }); | ||
}); | ||
|
||
it("sticks to object with int key when defined", () => { | ||
const obj = { x: "y", foo: { 0: "a" } }; | ||
dot.setter(obj, "foo.0", "b"); | ||
expect(obj).toEqual({ x: "y", foo: { 0: "b" } }); | ||
}); | ||
|
||
// We are currently don't support bracket path | ||
|
||
// it("supports bracket path", () => { | ||
// const obj = { x: "y" }; | ||
// dot.setter(obj, "nested[0]", "value"); | ||
// expect(obj).toEqual({ x: "y", nested: ["value"] }); | ||
// }); | ||
|
||
it("supports path containing key of the object", () => { | ||
const obj = { x: "y" }; | ||
dot.setter(obj, "a.x.c", "value"); | ||
expect(obj).toEqual({ x: "y", a: { x: { c: "value" } } }); | ||
}); | ||
|
||
it("can convert primitives to objects before setting", () => { | ||
const obj = { x: [{ y: true }] }; | ||
dot.setter(obj, "x.0.y.z", true); | ||
expect(obj).toEqual({ x: [{ y: { z: true } }] }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
export type DotPath = string | string[]; | ||
|
||
/** | ||
* Get value with given path | ||
*/ | ||
|
||
const getter = (obj: any, path: DotPath, defaultValue?: any): any => { | ||
path = toPath(path); | ||
let index = 0; | ||
while (obj && index < path.length) { | ||
obj = obj[path[index++]]; | ||
} | ||
return obj === undefined ? defaultValue : obj; | ||
}; | ||
|
||
/** | ||
* Set value with given path | ||
*/ | ||
|
||
const setter = (obj: any, path: DotPath, value: any) => { | ||
if (!isObject) return obj; | ||
path = toPath(path); | ||
|
||
let index = -1; | ||
const length = path.length; | ||
const lastIndex = length - 1; | ||
let nested = obj; | ||
|
||
while (nested != null && ++index < length) { | ||
const key = path[index]; | ||
let newValue = value; | ||
|
||
if (index !== lastIndex) { | ||
const objValue = nested[key]; | ||
newValue = isObject(objValue) | ||
? objValue | ||
: isInteger(path[index + 1]) | ||
? [] | ||
: {}; | ||
} | ||
|
||
if (newValue === undefined) { | ||
delete nested[key]; | ||
} else { | ||
nested[key] = newValue; | ||
} | ||
|
||
nested = nested[key]; | ||
} | ||
}; | ||
|
||
export default { | ||
getter, | ||
setter, | ||
}; | ||
|
||
const toPath = (path: DotPath): string[] => { | ||
if (Array.isArray(path)) return path; | ||
return path.split("."); | ||
}; | ||
|
||
/** | ||
* Checks if `value` is the object | ||
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) | ||
*/ | ||
|
||
const isObject = (value: any) => { | ||
const type = typeof value; | ||
return value !== null && (type === "object" || type === "function"); | ||
}; | ||
|
||
/** | ||
* Checks if `value` is the integer | ||
*/ | ||
|
||
const isInteger = (value: any): boolean => { | ||
return String(Math.floor(Number(value))) === value; | ||
}; |