3

I have two objects, obj1 = {key1: value1, key2: {key2_1: value2_1, key2_2: value2_2}} is for default values and obj2 = {key2: {key2_2: new_value2_2}} is for altered values (these come from config defaults and a parsed config file)

I want to combine these into one object where any value in obj2 replaces that in obj1 so long as it's not replacing an object, just a singular value. With the example above this would give obj1 = {key1: value1, key2: {key2_1: value2_1, key2_2: new_value2_2}}.

I've tried using the spread operator (...) but that ends up replacing the whole of key2 and essentially removing key2_1 in this case.

Any help would be very nice thanks

1
  • If you want a mechanism like this to be fully dynamic, I'm not sure there's any alternative but to implement your own recursive variant of Object.assign() that does what you want. Commented Dec 9, 2022 at 12:11

3 Answers 3

3

You could merge all values and check the inner object and merge these separately.

const
    merge = (a, b) => [a, b].reduce((r, o) => Object
        .entries(o)
        .reduce((q, [k, v]) => ({
            ...q,
            [k]: v && typeof v === 'object' ? merge(q[k] || {}, v) : v
        }), r),
    {}),
    obj1 = { key1: 'value1', key2: { key2_1: 'value2_1', key2_2: 'value2_2' } },
    obj2 = { key2: { key2_2: 'new_value2_2' } },
    result = merge(obj1, obj2);

console.log(result);

Sign up to request clarification or add additional context in comments.

Comments

2

You could create a function that will recursively merge properties of the two objects.

Properties of obj2 will take priority over obj1 and child objects will be merged key by key.

let obj1 = {key1: 'value1', key2: {key2_1: 'value2_1', key2_2: 'value2_2'}}
let obj2 = {key2: {key2_2: 'new_value2_2'}}

function merge(obj1, obj2) {
    const result = {};
    const combined = { ...obj1, ...obj2 };
    for(let k in combined) {
        if (combined[k] && (typeof(combined[k]) === 'object')) {
            result[k] = merge(obj1[k], obj2[k]);
        } else { 
            result[k] = obj2 ? obj2[k] || obj1[k] : obj1[k]
        }
    }
    return result;
}

console.log('Merged objects:', merge(obj1, obj2))
.as-console-wrapper { max-height: 100% !important; }

6 Comments

Might want to use ?? instead of || in that else clause, or even check only for undefined and leave null alone. Depends on the application, but the || will override 0 and "" etc. (edit wait this may be wrong ...) (no I think I'd be more cautious but I've had no coffee yet)
However this is basically the right answer.
Good point point, ?? is the more correct operator, thanks for the suggestion!
on the result[k] = line, it needed changing to obj2 ? obj2[k] || obj1[k] : obj1[k]. even with ?? instead of || it still throws an error from reading properties of undefined in some cases
@nxe you could use ?. to make that a little nicer
|
0

In case there is no better way of doing it, you can flatten both objects and use the spread operator on those.

With the examples I gave, flattening the objects would give flatten(obj1) = {"key1": value1, "key2.key2_1": value2_1, "key2.key2_2": value2_2} and flatten(obj2) = {"key2.key2_2": new_value2_2} (flatten funtion is below from here). You can then do {...flatten(obj1), ...flatten(obj2)} to give what I wanted


const flatten = (ob) => {
    let result = {};
    for (const i in ob) {
        if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) {
            const temp = flatten(ob[i]);
            for (const j in temp) {
                result[i + '.' + j] = temp[j];
            }
        } else {
            result[i] = ob[i];
        }
    }
    return result;
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.