如何合并两个JavaScript对象的属性并优先选择非null值?

7

这里有两个对象:

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

如何合并这两个对象并得到以下对象?
{a: "a", b: "b"}

我可以做到这一点:
const merged = {...obj1, ...obj2}

但它返回了这个结果:
{ a: "a", b: null }

有没有一种方法可以合并两个对象,同时更倾向于非空(非空、未定义等)值?

2
也许可以使用for循环,并进行显式的null检查? - Jonas Wilms
2
在合并对象之前进行筛选。 - kit
@kit 不错的想法,但这会导致循环两次。 - Ele
@Ele 不,它不是这样的。 - bestestefan
@stetoc 你能解释一下为什么不行吗?(: - Ele
显示剩余2条评论
13个回答

9
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
}

1
!answer[key] 对于像 "false" 或 "0" 这样的值将会是 true。我认为更好的方法是仅显式检查 null 值。 - Lix
也许你可以直接将obj1分配给answer,然后仅对obj2运行for循环? - Akash Agarwal
你必须使用 constletvar 声明变量。 - mwieczorek

4

我建议使用 LodashmergeWith 函数来实现:

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"}

注意:如果您的属性为未定义属性而不是空属性,则 _.merge(obj1, obj2) 将给出结果。 - Amit Levy

2

对于对象key,使用简单的forEach()循环怎么样?它可以同时处理nullundefined值:

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


1
尝试一下。

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


1
我认为我可以通过结合这些库来实现这个目标。
"@ramda/mergedeepwith" 是从 ramda 移植而来的。
"flat" 是用于展平/还原嵌套的 JavaScript 对象。
yarn add @ramda/mergedeepwith flat ramda

这是解决方法。

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

输出在这里。

$ 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
      ]
    }
  ]
}


这个解决方法被整合成了@nap5/use-merge。希望这能有所帮助。 - App-Club

1
这是被接受答案的修改版:
function merge(obj1, obj2) {
  let merged = { ...obj1 }

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

0

为了完整起见,有人应该提到 空值合并运算符

因此,对于顶层属性,您可以选择非空值的属性:

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"}

如果您还想复制更深层次的属性,事情就会变得非常棘手:

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?
          }), {})}))    

或许最好将其提取到一个函数中

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]
        }), {})
    }), {})

下一步是使用迭代器来完成,这可能比使用“reduce”函数和“??”运算符更简洁。

0

你可以创建一个函数,将对象数组作为参数传递。

这样,无论你有多少个对象,你都将得到它们合并在一起的结果,不包括 undefined 和 null 值。只需将它们作为数组发送即可。

在函数中,你需要传入所有的对象,进行映射,并使用for (const [key, value] of Object.entries(obj))遍历它们的键和值,然后排除那些值为 undefinednull 的对象。

请看下方示例:

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)


0

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)


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0
你可以扩展JavaScript的ObjectConstructor本身。添加一个名为merge的函数,它会覆盖下一个非空值并将所有对象的属性添加到一个单一对象中。
/* @/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))


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接