Merge objects updating duplicate keys (case-insensitively) - javascript

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);

Related

Query an array of objects and return the target

I need to find an object in an array by a key and value
I'm trying to do a search on an array of objects with for in. but I can not.
My input:
findObject[{ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }, { c: 3 }] //the last argument ({ c: 3 }) is the target
What I'm trying to return:
{ a: 1, b: { c: 3 } }
Note: The array of objects can have any object and the target too
You can use Array.find to find the item in the array whose values contain an object whose c property is 3:
const arr = [{ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }, { c: 3 }]
const result = arr.find(e => Object.values(e).some(k => k.c == 3))
console.log(result)
What the OP is looking for is a find using deep equality (keys and values, including nested keys and values are equal). Even shallow equality is a deep topic in JS and applying it recursively is even deeper.
A fast but flawed idea is comparing JSON encodings for the two objects being compared. I wave a hand at that in the snippet, but prefer to use a utility that has thought through edge cases and probably executes fast.
function isEqual(a, b) {
// not good, but quick to code:
// return JSON.stringify(a) === JSON.stringify(b)
// lodash has put some thought into it
return _.isEqual(a, b)
}
// find the first object in array with a value deeply equal to object
function findObject(array, object) {
return array.find(el => {
return Object.values(el).some(v => isEqual(v, object))
})
}
let array = [{ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3, d: { e: "hi" }} }];
let object = { c: 3, d: { e: "hi" }};
let result = findObject(array, object);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
The OP asks to find the last object of an array as the target in the array up to that point. Adopt as follows...
// array is the array to search plus the last item is the thing to search for
let firstObjects = array.slice(0, -1)
let lastObject = array.at(-1)
let result = findObject(firstObjects, lastObject)

Find top 3 values entries in a sorted JavaScript dictionary?

I have a sorted dictionary with certain number of entries:
dict = {B:3, A:2, C:2, D:1, E:0, F:0...}
Are there any ways to filter the dictionary to find the entries with top 3 largest values while considering duplicated values so the output will be? :
output = {B:3, A:2, C:2, D:1}
Thanks for reading..
You could count distinct values with a Set and filter ordered entries for getting an object from it.
const
object = { B: 3, A: 2, C: 2, D: 1, E: 0, F: 0 },
result = Object.fromEntries(Object
.entries(object)
.sort(([, a], [, b]) => b - a) // just to be sure
.filter((s => ([, v]) => s.add(v).size <= 3)(new Set))
);
console.log(result);
Maybe this can help you.
var dict = {
B: 3,
A: 2,
C: 2,
D: 1,
E: 0,
F: 0
};
// Create items array
var items = Object.keys(dict).map(function(key) {
return [key, dict[key]];
});
// Sort the array based on the second element
items.sort(function(first, second) {
return second[1] - first[1];
});
// Create a new array with only the first 3 items
let slicedArray = items.slice(0, 3);
//Return appropriate value
let dictSorted = slicedArray.reduce((a, x) => ({ ...a,
[x[0]]: x[1]
}), {});
console.log(dictSorted);

"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']));

Mapping over an array of objects without affecting all object fields

I have an array of objects which I need to parse in some way. The objects have a set of common fields and some optional fields that aren't present in all objects. For the sake of the example, a,b,c are common fields and d,e,f are optional. I want to perform some action on some of the fields, e.g double the value of a and capitalize d, while leaving the rest as they were. If an object was missing one or more of the optional fields, it should remain so in the result.
The catch is that I want to do it in a purely functional way, without declaring an empty array and pushing into it.
Example input:
const input = [
{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
Expected result:
[
{
a: 6,
b: 'test',
c: 34,
d: 'EXAMPLE'
},
{
a: 12,
b: 'another',
c: 0,
e: true,
f: () => {}
}
]
What I tried so far was using map like so:
const result = input.map(x => ({
a: 2 * x.a,
d: x.d.toUppercase()
});
Or like so:
const result = input.map(x => ({
a: 2 * x.a,
b,
c,
d: x.d.toUppercase(),
e,
f
});
But this results in objects that either contain only the fields which were manipulated, or all of them, regardless if they existed in the original object.
Any suggestions on how to accomplish that?
If you want your objects to be immutable, you will need to make a copy of the objects before making the needed changes. Object.assign allows you to do this in one expression:
const result = input.map(x => Object.assign({}, x,
{ a: 2 * x.a },
('d' in x) && { d: x.d.toUpperCase() }
));
const input = [{
a: 3,
b: 'test',
c: 34,
d: 'example'
}, {
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
const result = input.map(x => Object.assign({},
x,
{ a: 2 * x.a },
('d' in x) && { d: x.d.toUpperCase() }
));
console.log(result);
.as-console-wrapper { min-height: 100%; }
This solution requires Babel's Object rest spread transform plugin because Object Rest/Spread Properties for ECMAScript is still in the proposals stage.
Create a new object. Spread the original object into the new one. If the properties you wish to change exist in the original object, return an object with the the new property value to the spread. If not, return null (spread ignores undefined and null). The new values will override the old.
const input = [{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
const result = input.map(x => ({
...x, // spread all props of x
...('a' in x ? { a: 2 * x.a } : null), // override a if exists
...('d' in x ? { d: x.d.toUpperCase() } : null) // override d if exists
}));
console.log(result);
You could use the map method and make a check in each object if the key, whose value you want to change, exists. If so, then yuo can alter correspondingly the value of the key.
const input = [
{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
const result = input.map(function(obj){
if(obj.a){
obj.a = 2 * obj.a;
}
if(obj.d){
obj.d = obj.d.toUpperCase()
}
return obj;
});
console.log(result);
map function be still used.In this case you can check if a key a exist in the object then do the necessary operation only on it's value.
Hope this snippet will be useful
const input = [{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
input.map(function(elem, index) {
if (elem.hasOwnProperty('a')) {
elem.a = elem['a'] * 2;
}
})
console.log(input)
Inside the map function, you can first add the conditions to manipulate the input and then you need to return the modified object.
const input = [
{
a: 3,
b: 'test',
c: 34,
d: 'example'
},
{
a: 6,
b: 'another',
c: 0,
e: true,
f: () => {}
}
];
let output = input.map(o => {
if(o.a) { o.a = o.a * 2; }
if(o.d) { o.d = o.d.toUpperCase(); }
return o;
});
console.log(output)
Output::
[
{
"a": 6,
"b": "test",
"c": 34,
"d": "EXAMPLE"
},
{
"a": 12,
"b": "another",
"c": 0,
"e": true,
"f": () => {}
}
]
It might be worth googling for lenses. Many "functional" javascript libraries implement them to easily chain the kinds of data modifications you describe.
Under the hood, they might use the same Object.assign approach you've already found, but they allow for readable, reusable and easily chainable code.
A quick & dirty implementation to show the change in syntax (es6):
const propLens = prop => ({
get: obj => obj[prop],
// Return a new object with the property set to
// a value
set: (val, obj) => Object.assign({}, obj, {
[prop]: val
}),
// Return a new object with the property mapped
// by a function
over: (fn, obj) => Object.assign({}, obj, {
[prop]: fn(obj[prop])
})
});
// Helpers to create specific set/over functions
const set = (lens, val) => obj => lens.set(val, obj);
const over = (lens, fn) => obj => lens.over(fn, obj);
// Some utils for the example
const { pipe, double, toUpper } = utils();
// Now, you can define your modification by a set
// of lens + set/over combinations
const convertData = pipe(
set( propLens("d"), "added" ),
over( propLens("a"), double ),
over( propLens("b"), toUpper )
);
// Example data
const item = {a: 5, b: "hello", c: "untouched"};
// Converted copy
const convertedItem = convertData(item);
console.log("Converted:", convertedItem);
console.log("item !== convertedItem ->", item !== convertedItem);
// Utils
function utils() {
return {
pipe: (...fns) => x =>
fns.reduce((acc, f) => f(acc), x),
double: x => x * 2,
toUpper: x => x.toUpperCase()
};
}
(note that this example will run a bit slower than doing everything in one function, but you'll probably only notice for large amounts of data)
If you like the lenses-approach, I'd advice to use a well maintained and tested library (like Ramda). You'll even get more awesome features like index lenses and lens paths, and things are curried by default.

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

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

Categories