Javascript building an object directly - javascript

I have these values:
let buffer = {};
let value = 'value';
let x = 1;
let y = 2;
This is what I want to do:
buffer[x][y].value = value;
This is what I need to do, in order for it to work:
buffer[x] = {};
buffer[x][y] = {};
buffer[x][y].value = value;
My guess is that there is a better, maybe built in way to create an object like this in one step instead of three.

My guess is that there is a better, maybe built in way to create an object like this in one step instead of three.
What you have is fine, but you can also do it with computed properties (I'm assuming you need the values from variables) and shorthand property notation (for value):
let value = 'value';
let x = 1;
let y = 2;
let buffer = {
[x]: {
[y]: {
value
}
}
};
console.log(buffer);

If you want to create nested object based on a set of keys, you could use reduceRight. Use the rest parameters syntax to get the value and the array of paths as separate variables. Set the value as the initialValue parameter and add a level of nesting in each iteration
const create = (value, ...paths) => paths.reduceRight((r, k) => ({ [k]: r }), value)
console.log(create("final value", "1", "2", "value"))
console.log(create("final value", "any", "number", "of", "keys"))

Like this?
const buffer = {
1: {
2: {
value: 'value'
}
}
};
If x and y are already defined, then use computed properties:
let x = 1;
let y = 2;
const buffer = {
[x]: {
[y]: {
value: 'value'
}
}
};

You could reduce the given keys and assign the value at the last step.
const
setValue = (object, path, value) =>
path.reduce((o, k, i, { length }) => o[k] = i + 1 === length
? value
: o[k] || {}, object);
let buffer = {};
let value = 'value';
let x = 1;
let y = 2;
setValue(buffer, [x, y, value], value);
console.log(buffer);

Related

How to normalize JS Object with special char property

Hey guys I'm working on a project and I'm getting an object that looks like this:
{
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york",
}
How do I normalize that? So I can access the properties that I need?
Thanks
Here is a es5 approach to normalize the object
function normalisedObject(object) {
var normalised = {};
for (var key in object) {
//regex to capture all [.*] groups
var matches = key.match(/\[(.*?)\]/g);
if (matches) {
var temp = normalised;
while (matches.length > 0) {
//get first key and replace []
var newKey = matches[0].replace(/\[|\]/g, "");
if (matches.length !== 1) {
//keep traverse if the left over keys are greater than 1
temp = temp[newKey] || (temp[newKey] = {}); //assign empty object
} else {
temp[newKey] = object[key];
}
//poll
matches.shift();
}
}
}
return normalised;
}
//example
const t1 = performance.now();
var object = {
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york"
};
var normalised = normalisedObject(object);
const t2 = performance.now();
console.log(`Operation took ${t2 - t1}ms`);
console.log(normalised);
When an object's keys don't allow you to use simple dot notation, ie obj.property, you can use square-bracket notation instead, eg
const lang = obj["userChoice[language]"]
But I have a feeling you'd actually like to transform that object into something resembling this
{
userChoice: {
language: "en",
responses: {
favColor: "black",
favCity: "new york"
}
}
}
If that's the case, you need to reduce the object entries (key / value pairs) to a new object, parsing out the key paths and building new, inner objects as you go
const obj = {
"userChoice[language]": "en",
"userChoice[responses][favColor]": "black",
"userChoice[responses][favCity]": "new york",
}
const t1 = performance.now()
const normalised = Object.entries(obj).reduce((c, [ key, val ]) => {
// get the key parts as a path array
const path = key.replace(/]/g, "").split(/\[/)
// pop the last property for assigning the value later
const prop = path.pop()
// determine the inner-most object by path
const inner = path.reduce((o, k) => {
// create a new object if it doesn't exist
return o[k] ?? (o[k] = {})
}, c)
// assign the value
inner[prop] = val
return c
}, {})
const t2 = performance.now()
console.info(normalised)
console.log(`Operation took ${t2 - t1}ms`)
.as-console-wrapper { max-height: 100% !important; }
Note that if you start throwing any array properties in, eg userChoice[foo][0], this won't work.

How do I turn a string in dot notation into a nested object with a value?

I was trying to do this with split and reduce but I can't figure it out.
Here is my string and value
const str = "stack.overflow.is.cool"
const value = "Indeed"
I would like to turn this into
{ stack:
{ overflow:
{ is:
{ cool: indeed }
}
}
}
With map. Also possible with reduce and recursive function..
const str = "stack.overflow.is.cool"
const value = "Indeed"
let obj = {};
let pointer = obj;
str.split('.').map( (key, index, arr) => {
pointer = (pointer[key] = (index == arr.length - 1? value: {}))
});
console.log(JSON.stringify(obj, null, ' '));

manipulate the object value without knowing its key

I have an object obj and I want to manipulate it's value, but I don't want to write the value hard-coded something like below, is there any better alternative to this below approach
let obj = {a:{x:0}, b:{y:0}};
obj.a[Object.keys(obj.a)[0]] = 1;
console.log(obj);
I suppose you want to loop through them and have different values for x, y or whatever the key is
let obj = {
a: {
x: 0
},
b: {
y: 0
}
};
keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
key2 = Object.keys(obj[keys[i]])[0];
// instead of some_value inject an array of values
obj[keys[i]][key2] = 'some_value';
}
console.log(obj);
I have created a generic function where you can set the value of key without knowing the property names of inside object.
You can call the function with required key and value and get the desired result.
let obj = {a:{x:0}, b:{y:0}};
function assignValue(key, value) {
obj[key][Object.keys(obj[key])] = value;
}
assignValue('a', 1)
console.log(obj)
let objMultipleInnerKeys = {a:{x:0, z:2}, b:{y:0}};
function assignValueMultipleInnerKeys(key, innerKey, value) {
objMultipleInnerKeys[key][innerKey] = value;
}
assignValueMultipleInnerKeys('a', 'x', 1)
console.log(objMultipleInnerKeys)

Is it possible to do conditional destructuring or have a fallback?

I have an object that has lots of deeply nested properties. I want to be able to access properties on "MY_KEY" (below), but if that doesn't exist then get "MY_OTHER_KEY". How can I accomplish that?
const {
X: {
Y: {
MY_KEY: {
Values: segments = []
} = {}
} = {}
} = {}
} = segment;
You could achieve this using a temporal variable inside your destructuring assignment, something like this:
function destructure(segments) {
const {
X: {
Y: {
MY_OTHER_KEY: _fallback_value = {},
MY_KEY: {
Values: segment = []
} = _fallback_value,
} = {},
} = {},
} = segments;
return segment;
}
console.log(destructure({})); // []
console.log(destructure({X:{}})); // []
console.log(destructure({X:{Y:{MY_KEY:{Values:"A"}}}})); // A
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"B"}}}})); // B
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"C"}, MY_KEY:{Values:"D"}}}})); // D
First of all, this kind of destructuring will attempt to extract the second key all the time, which might have some unintended implications, like a property getter for MY_OTHER_KEY will always run.
However I fail to see the beauty in it. Hiding some control flow inside destructuring is just confusing. I would rather suggest extracting the parent object and use regular property access on it:
function destructure(segments) {
const {
X: {
Y: nested = {},
} = {},
} = segments;
const selected = nested.MY_KEY || nested.MY_OTHER_KEY || {};
const {
Values: segment = []
} = selected;
return segment;
}
console.log(destructure({})); // []
console.log(destructure({X:{}})); // []
console.log(destructure({X:{Y:{MY_KEY:{Values:"A"}}}})); // A
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"B"}}}})); // B
console.log(destructure({X:{Y:{MY_OTHER_KEY:{Values:"C"}, MY_KEY:{Values:"D"}}}})); // D
While object destructuring is cool and new, it is not the best in all cases. May do:
try {
var segments = segment.X.Y.MY_KEY.Values;
} catch(e){
//whatever
}
Check if the target is defined
let {/* do destructuring stuff */}
if (MY_KEY === undefined) {
// define `MY_OTHER_KEY` variable here
}
If you are trying to assign target to a different property if the previous target definition is undefined you can assign the variable at subsequent target definition
const segment = {Y:123};
let or = "Y";
let X;
({X, X = X || segment[or]} = segment);
console.log(X);

Accessing or creating nested JavaScript objects with string key without eval

I am looking for a nice solution to access a property by string value, but if the property does not exist it should create it. If the root structure already has defined some parts of the structure, the attributes should not be overwritten, but merged instead.
For example if you have an empty object test and you want to set a deep structure without using eval. e.g.
test = {}
test.foo.name = "Hallo" // <<- foo is an Object
test.foo[3] = "Test" // <<- foo should remain as Object, not as Array
test.foo.data[3].bar = 100 // <<- should not overwrite test.foo.name
I have written a solution that actually works, but it is quite bad code, I guess:
Also available as jsfiddle: https://jsfiddle.net/gvaLzqqf/4/
Object.setValue = function(node, flatKey, value) {
flatKey = flatKey.replace("[", ".");
flatKey = flatKey.replace("]", "");
var parts = flatKey.split(".")
var oldNode = node
parts.forEach(function(key, index) {
if (/^\+?(0|[1-9]\d*)$/.test(key)) {
key = key * 1
if (index > 0) {
var oldValue = parts[index - 1]
if (!Array.isArray(oldNode[oldValue])) {
oldNode[oldValue] = []
node = oldNode[oldValue]
}
}
}
if (node[key] == undefined) {
node[key] = {}
}
oldNode = node
node = node[key]
}); // for each
oldNode[parts[parts.length - 1]] = value
return oldNode[parts[parts.length - 1]]
} // function
var test = {}
Object.setValue(test, "foo.name", "Mr. Foo")
Object.setValue(test, "foo.data[0].bar", 100)
Object.setValue(test, "and.another[2].deep", 20)
console.log("test = " + JSON.stringify(test))
console.log("test.foo.data[0].bar = " + test.foo.data[0].bar)
How ever, is there any better way to achieve this?
You could split the path and reduce the path by walking the given object. If no Object exist, create a new property with the name, or an array. Later assign the value.
function setValue(object, path, value) {
var way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
var test = {};
setValue(test, "foo.name", "Mr. Foo");
setValue(test, "foo.data[0].bar", 100);
setValue(test, "and.another[2].deep", 20);
console.log(test);
I wouldn't reinvent the wheel in this case, and instead use lodash. Specifically the set() function. As per their example:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5

Categories

Resources