I implemented a method, to create an object based on a series of strings that are passed to it, of any depth or length. The way I implemented the solution was to map over the arguments. Assuming that strings are passed in the form x.a
, it then splits the string by the '.', returning just a single object with that key if the array only had one element otherwise, it was passed through a reducer to produce an array of objects which would then be merged into one object.
I'd like to make this better/ are there any concerns with using a reducer in a map function, since it's on the order of O(n2)?
type TKeys = string[]
type TTarget = {
[key: string]: TTarget | string,
}
type TTemplate = {
target: TTarget,
props: ReadonlyArray<string>
}
interface ITemplate {
target: {
[key: string]: string | TTemplate["target"]
},
props: ReadonlyArray<string>
}
const createObject = <T>(...keys: TKeys): T => (
keys.map(key => {
const props = key.split('.');
const reducerTemplate: ITemplate = {
target: {},
props: ['']
}
return props.length < 2
? {
[key]: ''
}
: props.reduce((obj: ITemplate, prop: string) => {
const target: ITemplate["target"] = obj.target
if (obj.props.length === 0) {
console.log(obj, prop);
obj.props = [prop];
target[prop] = {};
return obj;
}
const object: ITemplate["target"] = obj
.props
.reduce((prev: TTemplate["target"] , next: string) => (target[next] || prev[next]), {})
const atEnd: boolean = props.indexOf(prop) === props.length - 1
object[prop] = atEnd
? ''
: {}
obj.props = [
...obj.props,
prop
];
return atEnd
? target
: obj;
}, reducerTemplate)
}).reduce((main: {} | TTarget, obj: TTarget ): TTarget => ({
...main,
...obj
}), {}))
Example Usage:
createObject('a.b.c.d', 'hello.world', 'test')
//outputs
{
a: {
b: {
c: {
d: '',
},
},
},
hello: {
world: '',
},
test: '',
}
createObject('hello')
//outputs
{ hello: '' }
createObject('a.b')
//outputs
{
a: {
b: '',
},
};
Cannot set property 'a' of undefined
given your first example. \$\endgroup\$