Conditionally assign from part of an object - javascript

I think there's an elegant way to do this (maybe using destructuring), but I'm stumped so far. I'd like to assign some of the keys and values from one object to another, conditionally based on a boolean.
function bigObject() {
return { /* an object with lots of keys and values, lots */ }
}
function myFunction(aBool) {
// I want smallObject to be either empty or contain a subset of bigObject, based on aBool
// here's where I am stuck
let smallObject = aBool ? { keyA, keyB } = bigObject() : {};
}
JS complains that keyA is a reference error, and I can see how, even if I had this right, I'd just end up creating keyA and keyB variables, not smallObject as I want.
I know I can do this:
let smallObject = {};
if (aBool) {
let bigObject = bigObject();
smallObject.keyA = bigObject.keyA;
smallObject.keyB = bigObject.keyB;
smallObject.keyC = bigObject.keyC;
// 5 of these
}
But that sure is a lot of stuff to write when I think there's a way I can write just the props names.

It's not a problem to use temporary destructuring variables. Just use them again to build the target object. If you want it in one expression, you can use an inline function that performs the destructuring in its parameter variables, and then returns the new object:
let smallObject = aBool ? (
({ keyA, keyB }) => ({ keyA, keyB })
) (bigObject()) : {};
const bigObject = () => ({ keyA: 1, keyB: 2, keyC: 3, keyD: 4});
let aBool = true;
let smallObject = aBool ? (
({ keyA, keyB }) => ({ keyA, keyB })
) (bigObject()) : {};
console.log(smallObject);

You could use an IIFE that returns a destructured object.
const bigObject = {
keyA: 'a',
keyB: 'b',
keyC: 'c',
};
const getObj = (aBool) =>
aBool
? (({ keyA, keyB }) => ({ keyA, keyB }))(bigObject)
: {};
console.log(getObj(true));
Or you could write your own "plucking" function to return a new object based on the original:
const bigObject = {
keyA: 'a',
keyB: 'b',
keyC: 'c',
};
const pluck = (obj, callback) => callback(obj);
const getObj = (aBool) =>
aBool
? pluck(bigObject, ({ keyA, keyB }) => ({ keyA, keyB }))
: {};
console.log(getObj(true));

Option 1
if (aBool) smallObject = { ...smallObject, ...bigObject};
Option 2
if (aBool) {
Object.entries(bigObject).forEach(([key, value]) => smallObject[key] = value);
}

Related

Can't implement simple optional chaining

I am trying to implement a simple optional chaining state update. What I want is for the items to ONLY be changed IF the item is defined/exists; if not/is undefined, I want the item to keep the previous state (e.g userID should remain = 2 if not updated).
To test this out I created an object with three variables:
const userObj = {
firstName: "",
lastName: "",
userID: 2,
};
Then I created a function to update state:
const updateState = (item) => {
return {
userObj.firstName = item?.firstName,
userObj.lastName = item?.lastName,
userObj.userID = item?.userID,
};
};
Finally I pass the item which contains only one item to update (firstName) and I call the function:
const item = {
firstName: "None",
};
console.log(updateState(item));
The output:
userObj.firstName = item?.firstName,
^
SyntaxError: Unexpected token '.'
But when I hover over userObj I can see its properties:
You get this error because what you are trying to return is an object with dots in its keys.
You're mixing assignment and object creation. In addition item?.firstName only helps with the case that item is null or undefined in a sense that it fails fast instead of throwing an exception because you can't access null.firstName.
For this kind of default you'd have to do something along the lines of typeof item.firstName === 'string' ? item.firstName : userObj.firstName.
To pull everything together:
const func = (value, fallback) => (
typeof value === typeof fallback
? value
: fallback
);
const updateState = (item) => {
userObj.firstName = func(item?.firstName, item.firstName);
userObj.lastName = func(item?.lastName, item.lastName);
userObj.userID = func(item?.userID, item.userId);
return userObj;
};
Note however that this function will mutate userObj.
Edit: default was a poor naming choice.
You can do it this way, using javascript object destructuring
const updateState = (item) => {
return {
...userObj,
...item,
};
};
so it will only update the key and value that was passed, i hope the question is not on typescript
You can use null coalescing in conjunction with optional chaining:
const updateState = item => ({
userObj.firstName = item?.firstName ?? userObj.firstName ,
userObj.lastName = item?.lastName ?? userObj.lastName ,
userObj.userID = item?.userID ?? userObj.userId ,
});
You could use the spread operator:
const updateState = item => {
userObj = { ...userObj, ...item };
return userObj;
}
Or you can use lodash's defaults() function:
const _ = require('lodash');
const updateState = item => {
userObj = _.defaults(userObj, item)
return userObj;
}
Or... if you really want to mutate the state object, rather than creating a new one and replacing it, roll your own, similar:
const updateState = item => {
for (const key in item ) {
const hasValue = key != null && key != undefined && key != NaN ;
if ( hasValue ) {
userObj[prop] = item[prop];
}
}
}
There is, as they say, more than one way to skin a cat.
[Edited: Add explanation of the spread operator]
The spread operator,
const obj = { ...obj1, ...obj2, . . . , ...objN };
is somewhat akin to calling a function like this:
const obj = mergeObjects( obj1, obj2, . . ., objN );
where mergeObjects() is defined as:
function mergeObjects(...objects) {
const mergedObject = {};
for (const obj of objects ) {
for (const key in obj ) {
mergedObject[key] = item[key];
}
}
return mergedObject;
}
Or perhaps a better explanation might be done using Object.assign(). One could say that an expression like:
const obj = {
prop1: 'a' ,
prop2: 'b' ,
...obj1 ,
prop3: 'c' ,
prop4: 'd' ,
...obj2 ,
prop5: 'e' ,
prop6: 'f' ,
...obj3 ,
}
is the equivalent of this:
const obj = Object.assign( {},
{
prop1: 'a' ,
prop2: 'b' ,
},
obj1 ,
{
prop3: 'c' ,
prop4: 'd' ,
} ,
obj2 ,
{
prop5: 'e' ,
prop6: 'f' ,
} ,
obj3 ,
);

How can I merge multiple arrays into multiple separate objects?

I have an object that has multiple arrays that looks something like this.
let obj = {
links: ["https://somelink.com/image.jpg", "https://somelink.com/image2.jpg"],
IDs: ["yCmj", "4q1K"],
}
I want to make it so that it's turned into an array of objects. Like the following.
let newObj = {
templates: [
{id:"yCmj", link: "https://somelink.com/image.jpg"},
{id:"4q1K", link: "https://somelink.com/image2.jpg"}
]
}
What I have tried:
I tried mapping the object values to a new array but the second map overwrites the whole array.
let templates = obj.templateIDs.map((id) => ({id}))
templates = obj.thumbnailLinks.map((thumbnailLink) => ({thumbnailLink}))
let newObj = templates
The easiest way to accomplish this would be to pick either obj.links or obj.IDs and map their values to an object and use the index to locate the corresponding parallel value.
const obj = {
links: ["https://somelink.com/image.jpg", "https://somelink.com/image2.jpg"],
IDs: ["yCmj", "4q1K"],
};
const newObj = {
templates: obj.links.map((link, index) => (id => ({ id, link }))(obj.IDs[index]))
};
console.log(newObj);
.as-console-wrapper { top: 0; max-height: 100% !important; }
This operation is typically called "zipping" 2 arrays:
const zip = (a,b, zipper) => {
if(a.length != b.length)
throw "arrays must be the same length";
return a.map( (v,i) => zipper(v,b[i]))
}
const obj = {
links: ["https://somelink.com/image.jpg", "https://somelink.com/image2.jpg"],
IDs: ["yCmj", "4q1K"],
}
const newObj = {
templates: zip(obj.links, obj.IDs, (link,id) => ({link,id}))
}
console.log(newObj);
You can simply use map.
const obj = {
links: ["https://somelink.com/image.jpg", "https://somelink.com/image2.jpg"],
IDs: ["yCmj", "4q1K"],
};
const newObj = {
templates: obj.links.map((link, i) => ({ link, id: obj.IDs[i] })),
};
console.log(newObj);
let obj = {
links: ["https://somelink.com/image.jpg", "https://somelink.com/image2.jpg","https://somelink.com/image2.jpg"],
IDs: ["yCmj", "4q1K","34er"],
}
let propsKey = Object.keys(obj)
let finalArr = obj.links.map( ()=> {
return {}
})
propsKey.forEach( (prop) => {
obj[prop].forEach( (v,i)=>finalArr[i][prop] = v )
})
console.log(finalArr)

What's the best way of creating a new object with updated nested properties in modern JavaScript

I have a JavaScript object with some nested properties that I want to update based on some conditions. The starting object could be something like:
const options = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
}
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
}
}
};
I have come up with a solution using Object.keys, reduce and the spread operator, but I would like to know if this is the best / more concise way as of today or if there is a better way. I'm not looking for the most performing option, but for a "best practice" (if there is one) or a more elegant way.
EDIT 30/01/20
As pointed out in the comments by #CertainPerformance my code was mutating the original options variable, so I am changing the line const option = options[key]; to const option = { ...options[key] };. I hope this is correct and that the function is not mutating the original data.
const newObject = Object.keys(options).reduce((obj, key) => {
const option = { ...options[key] };
const newVal = getNewValue(option.label); // example function to get new values
// update based on existence of new value and key
if (option.selections && option.selections.value && newVal) {
option.selections.value = newVal;
}
return {
...obj,
[key]: option,
};
}, {});
getNewValue is an invented name for a function that I am calling in order to get an 'updated' version of the value I am looking at. In order to reproduce my situation you could just replace
the line const newVal = getNewValue(option.label); with const newVal = "bla bla";
Since you tagged this q with functional-programming here is a functional approach. Functional Lenses are an advanced FP tool and hence hard to grasp for newbies. This is just an illustration to give you an idea of how you can solve almost all tasks and issues related to getters/setters with a single approach:
// functional primitives
const _const = x => y => x;
// Identity type
const Id = x => ({tag: "Id", runId: x});
const idMap = f => tx =>
Id(f(tx.runId));
function* objKeys(o) {
for (let prop in o) {
yield prop;
}
}
// Object auxiliary functions
const objSet = (k, v) => o =>
objSetx(k, v) (objClone(o));
const objSetx = (k, v) => o =>
(o[k] = v, o);
const objDel = k => o =>
objDelx(k) (objClone(o));
const objDelx = k => o =>
(delete o[k], o);
const objClone = o => {
const p = {};
for (k of objKeys(o))
Object.defineProperty(
p, k, Object.getOwnPropertyDescriptor(o, k));
return p;
};
// Lens type
const Lens = x => ({tag: "Lens", runLens: x});
const objLens_ = ({set, del}) => k => // Object lens
Lens(map => ft => o =>
map(v => {
if (v === null)
return del(k) (o);
else
return set(k, v) (o)
}) (ft(o[k])));
const objLens = objLens_({set: objSet, del: objDel});
const lensComp3 = tx => ty => tz => // lens composition
Lens(map => ft =>
tx.runLens(map) (ty.runLens(map) (tz.runLens(map) (ft))));
const lensSet = tx => v => o => // set operation for lenses
tx.runLens(idMap) (_const(Id(v))) (o);
// MAIN
const options = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
}
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
}
}
};
const nameLens = lensComp3(
objLens("formatOption"))
(objLens("selections"))
(objLens("name"));
const options_ = lensSet(nameLens) ("foo") (options).runId;
// deep update
console.log(options_);
// reuse of unaffected parts of the Object tree (structural sharing)
console.log(
options.heightOptions === options_.heightOptions); // true
This is only a teeny-tiny part of the Lens machinery. Functional lenses have the nice property to be composable and to utilize structural sharing for some cases.
If you want to set a value for a nested property in a immutable fashion,
then you should consider adopting a library rather than doing it manually.
In FP there is the concept of lenses
Ramda provides a nice implementation: https://ramdajs.com/docs/
const selectionsNameLens = R.lensPath(
['formatOption', 'selections', 'name'],
);
const setter = R.set(selectionsNameLens);
// ---
const data = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
},
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
},
},
};
console.log(
setter('Another Specific Format', data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
The first comment from CertainPerformance made me realize that I was mutating the original options variable. My first idea was to make a copy with the spread operator, but the spread operator only makes a shallow copy, so even in my edit I was still mutating the original object.
What I think is a solution is to create a new object with only the updated property, and to merge the two objects at the end of the reducer.
EDIT
The new object also needs to be merged with the original option.selections, otherwise I would still overwrite existing keys at that level (ie I would overwrite option.selections.name).
Here is the final code:
const newObject = Object.keys(options).reduce((obj, key) => {
const option = options[key];
const newVal = getNewValue(option.label); // example function to get new values
const newOption = {}; // create a new empty object
// update based on existence of new value and key
if (option.selections && option.selections.value && newVal) {
// fill the empty object with the updated value,
// merged with a copy of the original option.selections
newOption.selections = {
...option.selections,
value: newVal
};
}
return {
...obj, // accumulator
[key]: {
...option, // merge the old option
...newOption, // with the new one
},
};
}, {});
A more concise version that has been suggested to me would be to use forEach() instead of reduce(). In this case the only difficult part would be to clone the original object. One way would be to use lodash's _.cloneDeep(), but there are plenty of options (see here).
Here is the code:
const newObject = _.cloneDeep(options);
Object.keys(newObject).forEach(key => {
const newVal = getNewValue(newObject[key].label); // example function to get new values
// update based on existence of new value and key
if (newObject[key].selections && newObject[key].selections.value && newVal) {
newObject[key].selections.value = newVal;
}
});
The only problem is that forEach() changes values that are declared outside of the function, but reduce() can mutate its parameter (as it happened in my original solution), so the problem is not solved by using reduce() alone.
I'm not sure that this is the best solution, but it surely is much more readable for the average developer than my first try or the other solutions.

Iterate object keys to replace the selected one with desired keys using es6 array functions

I have a requirement to replace the available keys with the desired keys in an object for which I was trying to execute below code, which later I found out to be incorrect usage of filter for desired output. hence I need help in getting the desired results using es6 array functions.
const columns = Object.keys(someArray).filter((columnName) => {
if (someCheck === "somecheck") {
if (columnName === 'MyName') {
const newcolumnName = `Pranav`;
return newcolumnName;
} else if (columnName === 'YourName') {
const newcolumnName = `Alex`;
return newcolumnName;
}
} else {
return (columnName !== 'sometingelse') ? columnName : '';
}
}
);
Here the someArray is as below:
someArray{
abc:"djfhdjf",
xyz:"ssss",
MyName:"onename",
YourName:"somename",
sometingelse:'somevalue'
}
I am expecting columns to be:
columns{
abc:"djfhdjf",
xyz:"ssss",
Pranav:"onename",
Alex:"somename",
sometingelse:'somevalue'
}
Please suggest how can I achieve the above expected output?
Note: I dont want to use function keyword in callbacks to avoid eslint errors
You could filter the wanted keys for replacement and replace the keys by using a new key and eleting the old one.
const
object = { abc: "djfhdjf", xyz: "ssss", MyName: "onename", YourName: "somename", sometingelse: 'somevalue' },
replacements = { MyName: 'Pranav', YourName: 'Alex', sometingelse: '' };
Object
.keys(object)
.filter(k => k in replacements)
.forEach(k => {
object[replacements[k]] = object[k];
delete object[k];
});
console.log(object);
For generating an object, you could map new objects and assign them to a single object.
const
object = { abc: "djfhdjf", xyz: "ssss", MyName: "onename", YourName: "somename", sometingelse: 'somevalue' },
replacements = { MyName: 'Pranav', YourName: 'Alex', sometingelse: '' },
result = Object.assign(...Object
.entries(object)
.map(([k, v]) => ({ [k in replacements ? replacements[k] : k]: v }))
);
console.log(result);
const obj = {
abc: 'djfhdjf',
xyz: 'ssss',
MyName: 'onename',
YourName: 'somename',
sometingelse: 'somevalue'
};
const newObj = Object.keys(obj).reduce((acc, key) => {
if (key === 'MyName') {
acc.newMyName = obj[key];
} else if (key === 'YourName') {
acc.newYourName = obj[key];
} else {
acc[key] = obj[key];
}
return acc;
}, {});
console.log('newObj = ', newObj);
Here is my approach, a bit long solution, but its on purpose so you can see how to do it simple without too much abstraction:
const someArray = {
abc:"djfhdjf",
xyz:"ssss",
MyName:"onename",
YourName:"somename",
sometingelse:'somevalue'
}
let foo = Object.keys(someArray).map(key => {
if(key === 'MyName') {
return 'Alex'
} else if(key === 'YourName') {
key = 'Pranav'
}
return key;
})
let bar = Object.entries(someArray).map((el, i) => {
el[0] = res[i];
return el;
})
let baz = r.reduce((acc, el)=>{
acc[`${el[0]}`] = el[1];
return acc;
},{})
console.log(baz);
You could use .reduce like so. It uses a similar idea that Nina proposed by using an object to hold your replacements. Here I have used the spread syntax to add the changed key to the accumulated object, along with it's associated value.
const someArray = {abc: "djfhdjf", xyz: "ssss", MyName: "onename", YourName: "somename", sometingelse: 'somevalue'},
toUse = {MyName: "Pranav", YourName: "Alex"}, // define the keys you want to change and what they should change to
res = Object.keys(someArray).reduce((acc, key) =>
({...acc, [key in toUse ? toUse[key] : key]:someArray[key]})
, {});
console.log(res);
I am running a reduce on the keys of some array starting with an empty object. The ...acc spreads out all the properties in the reduced object. ...{ [keysMap[key] || key]: obj[key] } checks if the current key is present in keysMap.If it is present,it uses that key (keysMap[key]) otherwise it just uses the keys of the existing object.(|| key).Hope that makes sense
const renameKeys = (keysMap, obj) =>
Object.keys(obj).reduce(
(acc, key) => ({
...acc,
...{ [keysMap[key] || key]: obj[key] }
}),
{}
)
const columns = renameKeys({'MyName':'Pranav','YourName':'Alex'},someArray)

how to create object from specific another object

i have this type of object which fetched from Redis
{
'username': 'hamet',
'username_Type': 'string',
'meta': 'object',
'meta_Type': 'object',
'meta.avatar': '/avatar.png',
'meta.avatar_Type': 'string',
'meta.active': 'false',
'meta.active_Type': 'boolean',
'meta.someArr': 'array',
'meta.someArr_Type': 'array',
'meta.someArr.0': 'object',
'meta.someArr.0_Type': 'object',
'meta.someArr.0.field': '123',
'meta.someArr.0.field_Type': 'number',
'meta.someArr.1': 'object',
'meta.someArr.1_Type': 'object',
'meta.someArr.1.field': '321',
'meta.someArr.1.field_Type': 'number'
}
all i want is convert this object to valid object like this:
{
username: 'hamet',
meta: {
avatar: '/avatar.png',
active: false,
someArr: [
{ field: 123 },
{ field: 321 }
]
}
}
once i created iterated function, but there was a problem with that. is it possible to convert with Iterated function and how?
You could create object with value types that you will use for creating new instances of different data types and then use reduce() method to build your object.
const data = {"username":"hamet","username_Type":"string","meta":"object","meta_Type":"object","meta.avatar":"/avatar.png","meta.avatar_Type":"string","meta.active":"false","meta.active_Type":"boolean","meta.someArr":"array","meta.someArr_Type":"array","meta.someArr.0":"object","meta.someArr.0_Type":"object","meta.someArr.0.field":"123","meta.someArr.0.field_Type":"number","meta.someArr.1":"object","meta.someArr.1_Type":"object","meta.someArr.1.field":"321","meta.someArr.1.field_Type":"number"}
const result = {}
const create = {'string': String,'number': Number,'boolean': Boolean,'array': Array,'object': Object}
const findType = (key, obj) => obj[key]
Object.keys(data).forEach(key => {
if (!key.includes('Type')) {
key.split('.').reduce((r, e, i, arr) => {
let type = findType(key + '_Type', data);
let value = create[data[key]] || arr[i + 1] ? new create[type] : new create[type](data[key]).valueOf()
if (data[key] == 'false') value = false
r[e] = r[e] || value;
return r[e]
}, result)
}
})
console.log(result)
Get an array of keys with Object.keys(). Filter out the _Type keys. Sort the keys to ensure that parents (shorter) keys are first, since keys` order in an object is not ensured.
Reduce the array of keys, and for each key get it's value by type. If the type is not object/array use the actual key value. Iterate the result object with Array.forEach(), until you get to the leaf. Add the key with the value.
const obj = {"meta.someArr.1.field":"321","username":"hamet","username_Type":"string","meta":"object","meta_Type":"object","meta.avatar":"/avatar.png","meta.avatar_Type":"string","meta.active":"false","meta.active_Type":"boolean","meta.someArr":"array","meta.someArr_Type":"array","meta.someArr.0":"object","meta.someArr.0_Type":"object","meta.someArr.0.field":"123","meta.someArr.0.field_Type":"number","meta.someArr.1":"object","meta.someArr.1_Type":"object","meta.someArr.1.field_Type":"number"};
const byType = {
object: Object,
array: Array
};
const result = Object.keys(obj)
.filter((k) => !k.includes('_Type')) // remove Type keys
.sort((a, b) => a.length - b.length) // ensures that shorter (parent) keys are first
.reduce((r, k) => {
const type = obj[`${k}_Type`];
const valueByType = byType[type] && byType[type]();
const value = valueByType ? valueByType : obj[k];
const keys = k.split('.');
let current = r;
keys.forEach((key, i) => {
if(!(key in current)) current[key] = value;
else current = current[key];
});
return r;
}, {});
console.log(result);
const result = {};
function apply(obj, value, key, ...keys) {
if(keys.length) {
apply(obj[key] || (obj[key] = {}), value ...keys);
} else {
obj[key] = value;
}
}
for(const [key, value] of Object.entries(yourObj))
apply(result, value, ...key.split("."));
You could use a recursive approach to generate the nested structure. I havent included a check if key is a number so that it creates an array, thats your job ;)
If you prefer functional programming:
const apply = (obj, value, ...keys) => keys.slice(1).reduce((obj, key) => obj[key] || (obj[key] = {}), obj)[keys.pop()] = value;

Categories