简体   繁体   中英

How to merge properties of two JavaScript objects and prefer not null values?

Here are two objects:

const obj1 = {a: null, b: "b"} 
const obj2 = {a: "a", b: null}

How can I merge the two objects and get the following object?

{a: "a", b: "b"}

I can do this:

const merged = {...obj1, ...obj2}

But it returns this:

{ a: "a", b: null }

Is there a way to merge two objects while prefering not null (nor empty, undefined, etc.) values?

function merge(obj1, obj2) {
  answer = {}
  for(key in obj1) {
    if(answer[key] === undefined || answer[key] === null)
      answer[key] = obj1[key];
  }
  for(key in obj2) {
    if(answer[key] === undefined || answer[key] === null)
      answer[key] = obj2[key];
  }
  return answer
}

I suggest using Lodash mergeWith for this:

const obj1 = {a: null, b: "b"} 
const obj2 = {a: "a", b: null}

const result = _.mergeWith({}, obj1, obj2,
  (a, b) => b === null ? a : undefined
)

// result: {a: "a", b: "b"}

Try this.

 const obj1 = {a: null, b: "b"} const obj2 = {a: "a", b: null} const obj3 = {} for (var k in obj1) { obj3[k] = obj1[k] ? obj1[k] : obj2[k]; } console.log(obj3);

What about a simple forEach() loop on object key . It will work for both null and undefined values:

 const obj1 = {a: null, b: "b"}; const obj2 = {a: "a", b: null}; const merged = {}; Object.keys(obj1).forEach((key) => merged[[key]] = obj1[key] ? obj1[key] : obj2[key]); console.log(merged);

I figured I could accomplish this by combining these libraries.

" @ramda/mergedeepwith " is ported from ramda .

" flat " is Flatten/unflatten nested Javascript objects.

yarn add @ramda/mergedeepwith flat ramda

This is workaround.

 import { default as flat } from "flat"; import mergeDeepWith from "@ramda/mergedeepwith"; import * as R from "ramda"; const obj1 = { a: null, b: "bbb", c: { "c-1": "cccc", }, d: [ { "d-1": "ddd-1", }, { "d-2": null, }, { "d-3": [0, 1, "a", 100], // omit 0 }, ], }; const obj2 = { a: "aaa", b: null, c: { "c-1": null, }, d: [ { "d-1": null, }, { "d-2": "ddd-2", }, { "d-3": ["b", "c"], }, ], }; const flattenedObj1 = flat.flatten(obj1); const flattenedObj2 = flat.flatten(obj2); const mergedObj = R.mergeDeepWith( (x, y) => { if (x) return x; if (y) return y; return null; }, flattenedObj2, flattenedObj1 ); console.log(JSON.stringify(flat.unflatten(mergedObj), null, 2));

Output is here.

 $ node index.js { "a": "aaa", "b": "bbb", "c": { "c-1": "cccc" }, "d": [ { "d-1": "ddd-1" }, { "d-2": "ddd-2" }, { "d-3": [ "b", "c", "a", 100 ] } ] }

function omitNull (obj) {
  const keys = Object.keys(obj);
  const _obj = {};
  for (const key of keys) {
    const value = obj[key];
    if (value !== null) {
      _obj[key] = value;
    }
  }
  return _obj;
}

const merged = {
  ...omitNull(obj1),
  ...omitNull(obj2)
};
result = {};
for(key in obj1){
    result[key] = obj1[key] === null ? obj2[key] : obj1[key];
}

this merges two objects in one loop

You could make a function that gets an array of objects as a parameter.

This way, no matter how many objects you have, you will get the result of them merged together excluding the undefined and null values. Just send them as an array.

There you pass all your objects, map them, then iterate through their key,values with for (const [key, value] of Object.entries(obj)) and exclude the ones that are undefined or null

See below

 const obj1 = { a: null, b: "goodb", c: 0, } const obj2 = { a: "gooda", b: null, c: undefined } function cleanObjects(arr) { let o = {} arr.map((obj) => { for (const [key, value] of Object.entries(obj)) { typeof value === 'undefined' || value === null ? delete obj[key] : o[key] = value; } }) return o; } const result = cleanObjects([obj1, obj2]) console.log(result)

Here's a modified version of the accepted answer:

function merge(obj1, obj2) {
  let merged = { ...obj1 }

  for (key in obj2) {
    if (merged[key] === undefined || merged[key] === null)
      merged[key] = obj2[key];
  }
  return merged
}

You can extend the Javascript ObjectConstructor itself. Add a function merge , This overrides the next non-null value and adds all the objects' properties to one single object.

/* @/utils.js */

Object.merge = function (...objs) {
  const obj = {};
  objs.reduce((prevObj, currentObj) => {
    if (typeof prevObj === 'object' && typeof currentObj === 'object') Object.entries(currentObj).forEach(([k, v]) => {
      obj[k] = v === null || v === undefined
        ? prevObj[k]
        : v;
    });
    return obj;
  }, {});
  return obj;
};

/* @/app.js */

Object.merge(a,b,c,...z);

 Object.merge = function (...objs) { const obj = {}; objs.reduce((prevObj, currentObj) => { if (typeof prevObj === 'object' && typeof currentObj === 'object') Object.entries(currentObj).forEach(([k, v]) => { obj[k] = v === null || v === undefined ? prevObj[k] : v; }); return obj; }, {}); return obj; } const john = { name: 'John Doe', age: 40, heigth: '5.3ft' } const jane = { name: 'Jane Doe', age: null, heigth: '4.1ft' } const mark = { name: 'Mark', age: 35, heigth: null } const ghost = { name: null, age: null, heigth: null, planet: 'unknown' } const noname = { name: null, age: 100, heigth: '100ft', planet: '100M-E' } console.log(Object.merge(john,jane,mark,ghost,noname))

 function merge(obj1, obj2) { if (!obj1 && !obj2) return null if (!obj1) return obj2; if (!obj2) return obj1; result = {} const keys = [...new Set([...Object.keys(obj1), ...Object.keys(obj2)])]; keys.forEach(key => { result[key] = obj1[key] || obj2[key] }) return result } console.log(merge(null, undefined)) console.log(merge({ a: 1}, null)) console.log(merge(null, { b : 2})) console.log(merge(null, { })) console.log(merge({a: 1, b : null, c : undefined, d: 'd'}, { b : 2, c: 5, d: null}))

 const obj1 = { a: null, b: "goodb", c: 0, d: 133, f: null } const obj2 = { a: "gooda", b: null, e: 1, c: undefined } function cleanObjects(arr) { let o = {} arr.map((obj) => { for (const [key, value] of Object.entries(obj)) { typeof value === 'undefined' || value === null? delete obj[key]: o[key] = value; } }) return o; } const result = cleanObjects([obj1, obj2]) console.log(result)

For completeness sake, someone should mention the Nullish coalescing operator

So for top-level properties you can choose the not-nullish one:

const obj1 = { a: null, b: "b" } 
const obj2 = { a: "a", b: null }
const obj3 = { foo: Infinity }

let merge = (...objects) => 
  objects
    .reduce((result, next) => ({ ...result, ...Object.entries(next)
      .reduce((resultingEntries, [key, value]) => ({ ...resultingEntries, [key]: value ?? result[key] }), {})}))

merge(obj1, obj2, obj3)
// {a: "a", b: "b", foo: "c"}

If you also want to copy deeper properties, things start to get REALLY ugly:

let merge2 = (...objects) => 
      objects
        .reduce((result, next) => ({ ...result, ...Object.entries(next)
          .reduce((resultingEntries, [key, value]) => ({ ...resultingEntries, [key]: 
            result && result[key] && typeof result[key] === 'object' // <- null unsafe here
              ? merge3(...objects.map(o => o && o[key]))
              : value ?? result[key] // <- to here?
          }), {})}))    

Perhaps better to extract it to a function

const getProps = (key, ...objects) => objects.filter(isntNullOrUndefined).map(o => o[key]).filter(isntNullOrUndefined)
const isntNullOrUndefined = x => x !== null && x !== undefined

const merge2 = (...objects) => 
  objects
    .filter(isntNullOrUndefined)
    .reduce((acc, obj) => ({
      ...acc,
      ...Object
        .keys(obj)
        .filter(key => !(key in acc))
        .filter(key => isntNullOrUndefined(obj[key]))
        .reduce((acc2, key) => ({
          ...acc2,
          [key]: typeof obj[key] === 'object'
            ? merge2(...getProps(key, ...objects))
            : getProps(key, ...objects)[0]
        }), {})
    }), {})

The next step is to do it with iterators, which might be slicker than reduce using the ?? operator.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM