(_.merge in ES6/ES7)Object.assign without overriding undefined values - javascript

There is _.merge functionality in lodash. I want to achieve the same thing in ES6 or ES7.
Having this snippet:
Object.assign({}, {key: 2}, {key: undefined})
I want to receive {key: 2}. Currently I receive {key: undefined}
This is NOT a deep merge.
Is it possible? If yes then how to achieve that?

You can't achieve that with a straight usage of Object.assign, because each next object will rewrite the same keys for prev merge. The only way, to filter your incoming objects with some hand-crafted function.
function filterObject(obj) {
const ret = {};
Object.keys(obj)
.filter((key) => obj[key] !== undefined)
.forEach((key) => ret[key] = obj[key]);
return ret;
}

You can simply filter out the keys with undefined values before passing them to Object.assign():
const assign = (target, ...sources) =>
Object.assign(target, ...sources.map(x =>
Object.entries(x)
.filter(([key, value]) => value !== undefined)
.reduce((obj, [key, value]) => (obj[key] = value, obj), {})
))
console.log(assign({}, {key: 2}, {key: undefined}))

Write a little utility to remove undefined values:
function removeUndefined(obj) {
for (let k in obj) if (obj[k] === undefined) delete obj[k];
return obj;
}
Then
Object.assign({}, {key: 2}, removeUndefined({key: undefined}))
This seems preferable to writing your own assign with wired-in behavior to remove undefined values.

use lodash to omit nil values and then combine the two objects into one via spread
{ ...(omitBy({key: 2}, isNil)), ...(omitBy({key: undefined}, isNil))}
See more info on lodash here https://lodash.com/docs/4.17.15

With ES2019/ES10's new object method, Object.fromEntries(), MichaƂ's answer can be updated:
const assign = (target, ...sources) =>
Object.assign(target, ...sources.map(x =>
Object.fromEntries(
Object.entries(x)
.filter(([key, value]) => value !== undefined)
)
))
console.log(assign({}, {key: 2}, {key: undefined}))

If you just need the values and don't need an object, you could also use object destructuring:
const input = { a: 0, b: "", c: false, d: null, e: undefined };
const { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 } = input;
console.log(a, b, c, d, e, f);
// => 0, "", false, null, 5, 6
This will only override absent or undefined values.
I often use this for function argument default values like this:
function f(options = {}) {
const { foo = 42, bar } = options;
console.log(foo, bar);
}
f();
// => 42, undefined
f({})
// => 42, undefined
f({ foo: 123 })
// => 123, undefined
f({ bar: 567 })
// => 42, 567
f({ foo: 123, bar: 567 })
// => 123, 567

Related

Merge objects updating duplicate keys (case-insensitively)

I'm using a javascript module that has a configuration object. One is already set with defaults and the user can pass in values to overwrite these set values. I am using Object.assign to merge the two objects.
Here is an example:
const target = { a: 1, b: 2 }; // <-- default config
const source = { B: 4, c: 5 }; // <-- User input config
Object.assign(target, source);
console.log(target); //{a: 1, b: 2, B: 4, c: 5}
In this example if the user accidentally types in an uppercase 'B' instead of a lowercase one then the config object adds another value to itself when what I really want is the lowercase 'b' to be updated.
I know that's the intended behavior of Object.assign but trying to make this easier for the user and be case insensitive.
This version is a little different from the others. It normalizes only the keys found in the initial object, leaving the others intact. Something like this:
insensitiveAssign ({a: 1, b: 2}, {B: 4, c: 5, D: 6}) //=> {a: 1, b: 4, c: 5, D: 6}
// ^ ^ ^ ^
// unaltered --------------' | | |
// overwritten ------------------+ | |
// added ------------------------------+ |
// added (note: key not modified) -----------+
That may or may not be of use to you, but it's an interesting approach to the problem. It also does not modify either of your objects, creating an altered clone instead.
const insensitiveAssign = (target, source) => {
const keys = Object .keys (target) .reduce ((a, k) => ((a[k.toLowerCase()] = k), a), {})
return Object .entries (source) .reduce ((a, [k, v]) => {
const lowerK = k.toLowerCase()
const key = lowerK in keys ? keys[lowerK] : k
a[key] = v;
return a
}, Object.assign({}, target)) // start with a shallow copy
}
const target = {a: 1, b: 2};
const source = {B: 4, c: 5, D: 6};
console .log (
'result:',
insensitiveAssign (target, source),
)
console .log (
'target:',
target,
)
console .log (
'source:',
source
)
Update
A comment updated the question to ask how this might be applied to nested objects. In actuality, I would probably try to write that from scratch, but I don't have time now and a (only slightly tested) modification of this seems like it would work:
const insensitiveAssign = (target, source) => {
// if-block added
if (Object(target) !== target || (Object(source) !== source)) {
return source
}
const keys = Object .keys (target) .reduce ((a, k) => ((a[k.toLowerCase()] = k), a), {})
return Object .entries (source) .reduce ((a, [k, v]) => {
const lowerK = k.toLowerCase()
const key = lowerK in keys ? keys[lowerK] : k
a[key] = insensitiveAssign(target[key], v); // this line updated
return a
}, Object.assign({}, target))
}
const target = {a: 1, b: 2, x: {w: 'a', y: {z: 42}}};
const source = {B: 4, c: 5, D: 6, x: {V: 'c', Y: {z: 101}}};
console .log (
'result:',
insensitiveAssign (target, source),
)
console .log (
'target:',
target,
)
console .log (
'source:',
source
)
You'll have to lowercase the object keys first, like done here
const target = { a: 1, b: 2 }; // <-- default config
const source = { B: 4, c: 5 }; // <-- User input config
const lowerSource = Object.keys(source).reduce((c, k) => (c[k.toLowerCase()] = source[k], c), {});
Object.assign(target, lowerSource);
console.log(target);
You may simply remap source object lower-casing its keys with Object.keys() and Array.prototype.map(), then pass resulting key-value pairs as parameter to Object.assign():
const target = { a: 1, b: 2 },
source = { B: 4, c: 5 },
result = Object.assign(
target,
...Object
.keys(source)
.map(key =>
({[key.toLowerCase()]: source[key]}))
)
console.log(result)
You can try something like below code.
target = { a: 1, b: 2 }; // <-- default config
source = { B: 4, c: 5 }; // <-- User input config
source = JSON.parse(JSON.stringify(source).toLowerCase())
Object.assign(target, source);

"Removing" multiple properties from an object without mutation

I'm searching a for a way to create a function. in which I can pass an object and an array of properties (keys) I want gone. That function will return me a new object that doesn't have the keys I've specified.
function(keys: array, obj: object) {...}
Question is - how do I do that with multiple properties?
I've searched and only found this kind of solution:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
But it only works if I want to remove only ONE key. What if I want to remove multiple, using an array I just passed? How do I do that without mutating the original array or manually creating copies of it?
You could destructure the object by taking a computed property for unwanted properties.
const
without = (object, keys) => keys.reduce((o, k) => {
const { [k]: _ , ...p } = o;
return p;
}, object),
myObject = { a: 1, b: 2, c: 3 },
keys = ['a', 'b'],
result = without(myObject, keys);
console.log(result);
You can do it using reduce and Object.entries(). You can try this:
const myObject = {
a: 1,
b: 2,
c: 3,
d: 4
};
const removeProps = (object, keys) => {
return Object.entries(object).reduce((a, [key, value]) => (keys.indexOf(key) === -1 ? {...a, [key]: value}: a), {});
}
console.log(removeProps(myObject, ['b']));
console.log(removeProps(myObject, ['b', 'c']));
console.log('Original Object: ', myObject);
.as-console-wrapper{min-height: 100%!important; top: 0}
Above answers are great, I'm sharing my try:
var myObject = { a: 1, b: 2, c: 3, d: 4};
let remove=(obj, arr)=> {
let output=[];
for(const [key, value] of Object.entries(obj)){
if(!arr.includes(key)){
output.push([key,value]);
}
}
return Object.fromEntries(output);
}
console.log(remove(myObject, ['a']));
console.log(remove(myObject, ['a', 'c']));

apply function to all instances of an attribute in an object tree

Let's say I have an object of unknown structure and depth, where I want to modify an attribute. For example, let's imagine that the attribute I want to change is id and the structure might be something like this:
"{
"a": "foo",
"b": "bar",
"c": {
"id": {
"a": "foo"
},
"b": "whatever"
},
"id": {
"a": "foo"
}
}"
Is there any stablished pattern or way to take any instance of a wanted key in the whole tree and apply a generic function to it? Ideally, checking array elements too if they existed.
In terms of functional programming, we need our structure to be a Functor, that is, supply it with a function fmap so that fmap(fn, someTree) will yield the same structure with fn applied to all values inside it. There are quite a few ways to implement that, for example:
let isObject = x => x && typeof x === 'object';
let map = fn => obj =>
Array.isArray(obj)
? obj.map((v, k) => fn([null, v]).pop())
: Object.fromEntries(Object.entries(obj).map(fn));
let _fmap = fn => ([key, val]) =>
isObject(val)
? [
key,
map(_fmap(fn))(val)
]
: fn([key, val]);
let fmap = fn => map(_fmap(fn));
//
const renameKey = (o, n) => ([key, val]) => (key === o ? [n, val] : [key, val]);
test = {
a: 1,
b: 2,
c: [
{a: 1}, {a: 2}, {b: 3}
]
};
newTest = fmap(renameKey('a', 'blah'))(test);
console.log(newTest);
Here, we treat a tree "node" as a [key, value] pair. The callback receives a pair and is supposed to return it back, with or without a modification.

How to remove `undefined` value from an object

I have an object like this
const obj = {a: 123, b: 'text', c: [1,2,3]}
I use it for route params, but c can be an empty array, I don't have to handle a and b because they surely will have value. Do I have to manually omit c from obj? making it only {a: 123, b: 'text'}?
Is there any way I don't have to manually omit the property from the object if the value is undefined?
If you're using lodash, you can use omitBy
_.omitBy({a: 123, b: 'text', c: undefined}, _.isUndefined)
Morever, you can combine many condition by chain
_({a:123,b:'text',c:undefined,d:null})
.omitBy(_.isUndefined)
.omitBy(_.isNull).value();
Or
You can introduce a function that help u omit undefined value for every objects
const obj = {a: 123, b: 'text', c: undefined};
const omitObj = obj => Object.keys(obj).reduce((acc, key) => {
if (obj[key] === undefined) {
return acc;
}
acc[key] = obj[key];
return acc;
}, {})
const newObj = omitObj(obj);
console.log(newObj);
In case you're not sure about the last param
just make a conditional statement
const obj = {a: 123, b: 'text', c: [1,2,3]}
var AnotherObj = obj
if (AnotherObj.c == undefined)
delete AnotherObj.c
if obj.c is defined everything will be fine
else it will delete the third param from the object

transform object to array with lodash

How can I transform a big object to array with lodash?
var obj = {
22: {name:"John", id:22, friends:[5,31,55], works:{books:[], films:[],}
12: {name:"Ivan", id:12, friends:[2,44,12], works:{books:[], films:[],}
}
// transform to
var arr = [{name:"John", id:22...},{name:"Ivan", id:12...}]
You can do
var arr = _.values(obj);
For documentation see here.
A modern native solution if anyone is interested:
const arr = Object.keys(obj).map(key => ({ key, value: obj[key] }));
or (not IE):
const arr = Object.entries(obj).map(([key, value]) => ({ key, value }));
_.toArray(obj);
Outputs as:
[
{
"name": "Ivan",
"id": 12,
"friends": [
2,
44,
12
],
"works": {
"books": [],
"films": []
}
},
{
"name": "John",
"id": 22,
"friends": [
5,
31,
55
],
"works": {
"books": [],
"films": []
}
}
]"
For me, this worked:
_.map(_.toPairs(data), d => _.fromPairs([d]));
It turns
{"a":"b", "c":"d", "e":"f"}
into
[{"a":"b"}, {"c":"d"}, {"e":"f"}]
There are quite a few ways to get the result you are after. Lets break them in categories:
ES6 Values only:
Main method for this is Object.values. But using Object.keys and Array.map you could as well get to the expected result:
Object.values(obj)
Object.keys(obj).map(k => obj[k])
var obj = {
A: {
name: "John"
},
B: {
name: "Ivan"
}
}
console.log('Object.values:', Object.values(obj))
console.log('Object.keys:', Object.keys(obj).map(k => obj[k]))
ES6 Key & Value:
Using map and ES6 dynamic/computed properties and destructuring you can retain the key and return an object from the map.
Object.keys(obj).map(k => ({[k]: obj[k]}))
Object.entries(obj).map(([k,v]) => ({[k]:v}))
var obj = {
A: {
name: "John"
},
B: {
name: "Ivan"
}
}
console.log('Object.keys:', Object.keys(obj).map(k => ({
[k]: obj[k]
})))
console.log('Object.entries:', Object.entries(obj).map(([k, v]) => ({
[k]: v
})))
Lodash Values only:
The method designed for this is _.values however there are "shortcuts" like _.map and the utility method _.toArray which would also return an array containing only the values from the object. You could also _.map though the _.keys and get the values from the object by using the obj[key] notation.
Note: _.map when passed an object would use its baseMap handler which is basically forEach on the object properties.
_.values(obj)
_.map(obj)
_.toArray(obj)
_.map(_.keys(obj), k => obj[k])
var obj = {
A: {
name: "John"
},
B: {
name: "Ivan"
}
}
console.log('values:', _.values(obj))
console.log('map:', _.map(obj))
console.log('toArray:', _.toArray(obj))
console.log('keys:', _.map(_.keys(obj), k => obj[k]))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Lodash Key & Value:
// Outputs an array with [[KEY, VALUE]]
_.entries(obj)
_.toPairs(obj)
// Outputs array with objects containing the keys and values
_.map(_.entries(obj), ([k,v]) => ({[k]:v}))
_.map(_.keys(obj), k => ({[k]: obj[k]}))
_.transform(obj, (r,c,k) => r.push({[k]:c}), [])
_.reduce(obj, (r,c,k) => (r.push({[k]:c}), r), [])
var obj = {
A: {
name: "John"
},
B: {
name: "Ivan"
}
}
// Outputs an array with [KEY, VALUE]
console.log('entries:', _.entries(obj))
console.log('toPairs:', _.toPairs(obj))
// Outputs array with objects containing the keys and values
console.log('entries:', _.map(_.entries(obj), ([k, v]) => ({
[k]: v
})))
console.log('keys:', _.map(_.keys(obj), k => ({
[k]: obj[k]
})))
console.log('transform:', _.transform(obj, (r, c, k) => r.push({
[k]: c
}), []))
console.log('reduce:', _.reduce(obj, (r, c, k) => (r.push({
[k]: c
}), r), []))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Note that in the above examples ES6 is used (arrow functions and dynamic properties).
You can use lodash _.fromPairs and other methods to compose an object if ES6 is an issue.
If you want the key (id in this case) to be a preserved as a property of each array item you can do
const arr = _(obj) //wrap object so that you can chain lodash methods
.mapValues((value, id)=>_.merge({}, value, {id})) //attach id to object
.values() //get the values of the result
.value() //unwrap array of objects
Transforming object to array with plain JavaScript's(ECMAScript-2016) Object.values:
var obj = {
22: {name:"John", id:22, friends:[5,31,55], works:{books:[], films:[]}},
12: {name:"Ivan", id:12, friends:[2,44,12], works:{books:[], films:[]}}
}
var values = Object.values(obj)
console.log(values);
If you also want to keep the keys use Object.entries and Array#map like this:
var obj = {
22: {name:"John", id:22, friends:[5,31,55], works:{books:[], films:[]}},
12: {name:"Ivan", id:12, friends:[2,44,12], works:{books:[], films:[]}}
}
var values = Object.entries(obj).map(([k, v]) => ({[k]: v}))
console.log(values);
Object to Array
Of all the answers I think this one is the best:
let arr = Object.entries(obj).map(([key, val]) => ({ key, ...val }))
that transforms:
{
a: { p: 1, q: 2},
b: { p: 3, q: 4}
}
to:
[
{ key: 'a', p: 1, q: 2 },
{ key: 'b', p: 3, q: 4 }
]
Array to Object
To transform back:
let obj = arr.reduce((obj, { key, ...val }) => { obj[key] = { ...val }; return obj; }, {})
To transform back keeping the key in the value:
let obj = arr.reduce((obj, { key, ...val }) => { obj[key] = { key, ...val }; return obj; }, {})
Will give:
{
a: { key: 'a', p: 1, q: 2 },
b: { key: 'b', p: 3, q: 4 }
}
For the last example you can also use lodash _.keyBy(arr, 'key') or _.keyBy(arr, i => i.key).
2017 update: Object.values, lodash values and toArray do it. And to preserve keys map and spread operator play nice:
// import { toArray, map } from 'lodash'
const map = _.map
const input = {
key: {
value: 'value'
}
}
const output = map(input, (value, key) => ({
key,
...value
}))
console.log(output)
// >> [{key: 'key', value: 'value'}])
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
var arr = _.map(obj)
You can use _.map function (of both lodash and underscore) with object as well, it will internally handle that case, iterate over each value and key with your iteratee, and finally return an array. Infact, you can use it without any iteratee (just _.map(obj)) if you just want a array of values. The good part is that, if you need any transformation in between, you can do it in one go.
Example:
var obj = {
key1: {id: 1, name: 'A'},
key2: {id: 2, name: 'B'},
key3: {id: 3, name: 'C'}
};
var array1 = _.map(obj, v=>v);
console.log('Array 1: ', array1);
/*Actually you don't need the callback v=>v if you
are not transforming anything in between, v=>v is default*/
//SO simply you can use
var array2 = _.map(obj);
console.log('Array 2: ', array2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
However, if you want to transform your object you can do so, even if you need to preserve the key, you can do that ( _.map(obj, (v, k) => {...}) with additional argument in map and then use it how you want.
However there are other Vanilla JS solution to this (as every lodash solution there should pure JS version of it) like:
Object.keys and then map them to values
Object.values (in ES-2017)
Object.entries and then map each key/value pairs (in ES-2017)
for...in loop and use each keys for feting values
And a lot more. But since this question is for lodash (and assuming someone already using it) then you don't need to think a lot about version, support of methods and error handling if those are not found.
There are other lodash solutions like _.values (more readable for specific perpose), or getting pairs and then map and so on. but in the case your code need flexibility that you can update it in future as you need to preserve keys or transforming values a bit, then the best solution is to use a single _.map as addresed in this answer. That will bt not that difficult as per readability also.
If you want some custom mapping (like original Array.prototype.map) of Object into an Array, you can just use _.forEach:
let myObject = {
key1: "value1",
key2: "value2",
// ...
};
let myNewArray = [];
_.forEach(myObject, (value, key) => {
myNewArray.push({
someNewKey: key,
someNewValue: value.toUpperCase() // just an example of new value based on original value
});
});
// myNewArray => [{ someNewKey: key1, someNewValue: 'VALUE1' }, ... ];
See lodash doc of _.forEach https://lodash.com/docs/#forEach

Categories