在对象中交换键和值

182

我有一个非常大的JSON对象,其结构如下:

{A : 1, B : 2, C : 3, D : 4}

我需要一个函数,可以交换我的对象中的键和值,但我不知道如何做。我需要的输出应该是这样的:

{1 : A, 2 : B, 3 : C, 4 : D}

有没有办法我可以手动创建一个新对象,其中所有内容都被交换?
谢谢


1
所有的值都是数字并且数字会重复吗?如果值会重复,那么你将无法交换它们,因为它们会覆盖其他值,除非你将它们的值更改为某个唯一的值。可能有更好的解决方案。需要交换的原因是什么? - Patrick Evans
@PatrickEvans 这些都是数字,但它们不重复。我正在尝试制作一个基本的密码。 - C1D
2
值得注意的是,像这样的“交换”操作至少有两个问题:1)当值变为键时,它们被转换为字符串,因此当您遇到意外的“[object Object]”键时,请不要感到惊讶。2)重复的值(在转换为字符串后)将被覆盖。至少可以通过生成Map而不是Object来解决第一个问题,如下所示:function swap(obj) {return new Map(Object.entries(x).map([k, v] => [v, k]))} - broofa
const swap = o => Object.entries(o).reduce((r, [k, v]) => (r[v]=k, r), {}); 可以使用类似的方法来处理值为正整数/索引的数组。 - mpapec
25个回答

185
function swap(json){
  var ret = {};
  for(var key in json){
    ret[json[key]] = key;
  }
  return ret;
}

这里有个例子FIDDLE,别忘了打开控制台查看结果。


ES6 版本:

static objectFlip(obj) {
  const ret = {};
  Object.keys(obj).forEach(key => {
    ret[obj[key]] = key;
  });
  return ret;
}

或者使用Array.reduce()Object.keys()方法

static objectFlip(obj) {
  return Object.keys(obj).reduce((ret, key) => {
    ret[obj[key]] = key;
    return ret;
  }, {});
}

或者使用Array.reduce()Object.entries()

static objectFlip(obj) {
  return Object.entries(obj).reduce((ret, entry) => {
    const [ key, value ] = entry;
    ret[ value ] = key;
    return ret;
  }, {});
}

1
Object.keys() + Array.prototype.reduce() 是最快的:https://jsbench.me/y2kzww53oz/1 - Amit Beckenstein

94

现在我们有了Object.fromEntries:

const f = obj => Object.fromEntries(Object.entries(obj).map(a => a.reverse()))

console.log(
  f({A : 'a', B : 'b', C : 'c'})
) // => {a : 'A', b : 'B', c : 'C'}

或者:

const f = obj => Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k]))

console.log(
  f({A : 'a', B : 'b', C : 'c'})
) // => {a : 'A', b : 'B', c : 'C'}

(已更新,删除了多余的括号 - 感谢@devin-g-rhode)


作为 Node.js 版本 12 的标准方式,应该有一种通用的对象交换方法。 - Damaged Organic
1
这是最易于理解和最美观的解决方案。我认为你可以在箭头回调的返回值上省略括号。 - Devin Rhode
虽然这确实是最优雅的解决方案,但它比使用 Object.entries() + Array.prototype.reduce() 稍慢一些:https://jsbench.me/y2kzww53oz/1 - Amit Beckenstein

64

你可以使用 lodash函数_.invert,它也可以使用多个值

 var object = { 'a': 1, 'b': 2, 'c': 1 };

  _.invert(object);
  // => { '1': 'c', '2': 'b' }

  // with `multiValue`
  _.invert(object, true);
  // => { '1': ['a', 'c'], '2': ['b'] }

1
不再支持multiValue。 - Rudy Huynh
自从 lodash 4 版本以后,你需要使用新的方法 _.invertBy() 来替代将 multiValue 设置为 true - white_gecko
Lodash invert是轻微领先的最快实现方式: http://jsben.ch/tSjlB 看一下它的实现确实很有趣: https://github.com/lodash/lodash/blob/master/invert.js - Sam Hasler

56

使用 ES6:

const obj = { a: "aaa", b: "bbb", c: "ccc", d: "ddd" };
Object.assign({}, ...Object.entries(obj).map(([a,b]) => ({ [b]: a })))

3
现在(2019年)似乎是一种更好的解决方案!有人能够确认吗?性能如何?语义是完美的:我们可以看到这确实是一个简单的“映射和交换”解决方案。 - Peter Krauss
5
@peter-krauss,欢迎您尝试修改http://jsben.ch/gHEtn,以防我遗漏了什么。但是,对比这个答案和jPO的答案,可以发现jPO的for循环性能比grappeq的map方法快近3倍(至少在我的浏览器上)。 - Michael Hays
我试过了,在我的电脑上比jPO的慢30%。 - yeah22
这里有几个实现的比较:http://jsben.ch/tSjlB - Sam Hasler

39

获取对象的键,然后使用数组的reduce函数遍历每个键,并将值设置为键,键设置为值。

const data = {
  A: 1,
  B: 2,
  C: 3,
  D: 4
}
const newData = Object.keys(data).reduce(function(obj, key) {
  obj[data[key]] = key;
  return obj;
}, {});
console.log(newData);


31
在ES6/ES2015中,您可以将Object.keysreduce与新的Object.assign函数、箭头函数计算属性名结合使用,以获得一个非常简单的单语句解决方案。
const foo = { a: 1, b: 2, c: 3 };
const bar = Object.keys(foo)
    .reduce((obj, key) => Object.assign({}, obj, { [foo[key]]: key }), {});

如果你正在使用对象展开运算符进行转译(截至撰写本文时为3级),这将进一步简化事情。
const foo = { a: 1, b: 2, c: 3 };
const bar = Object.keys(foo)
    .reduce((obj, key) => ({ ...obj, [foo[key]]: key }), {});

最后,如果您可以使用Object.entries(截至撰写本文为止已达到第4阶段),则可以使逻辑更加简洁。

const foo = { a: 1, b: 2, c: 3 };
const bar = Object.entries(foo)
    .reduce((obj, [key, value]) => ({ ...obj, [value]: key }), {});

语法错误:/Users/markus/Entwicklung/IT1_Beleg/public/es6/vokabeltrainer.js: 意外的标记 (53:45) 51 | if (btoa) { 52 | entries = Object.entries(entries)
53 | .reduce((obj, [key, value]) => ({...obj, [value]: key}), {}); | ^
- Superlokkus
@Superlokkus,我对该错误输出的最佳解释是您没有转译对象扩展语法,并且截至今天,我不知道有任何JavaScript引擎原生支持它。 - joslarson
1
看看 @grappeq 的解决方案,似乎更好(最简单的!)。 - Peter Krauss

17

2021年的答案

通过使用像这样的ES6语法来简洁地表达。

const obj = {A : 1, B : 2, C : 3, D : 4}

console.log(
  Object.entries(obj).reduce((acc, [key, value]) => (acc[value] = key, acc), {})
);

说明:

(acc[value] = key, acc)

使用逗号运算符(,)语法。

逗号运算符(,)从左到右依次计算并返回最后一个表达式的值。


1
这似乎是其中一种更快的实现方式(至少在Chrome 110中):http://jsben.ch/tSjlB 。Lodash实现方式略微更快。 - Sam Hasler

5
作为对 @joslarson 和 @jPO 答案的补充:
不需要 ES6,您可以使用 Object.keysArray.reduce逗号运算符
Object.keys(foo).reduce((obj, key) => (obj[foo[key]] = key, obj), {});

有些人可能会觉得它很丑,但这种方式比较快,因为reduce不会在每次循环中展开obj的所有属性。


尽管如此... jPO的答案表现几乎比这个好了将近2倍。http://jsben.ch/R75aI(我知道你的评论是很久以前的,但我正在评论grappeq的答案,并想运行你的示例)。 - Michael Hays
总的来说,for循环仍然比forEachmapreduce方法更快。 我提供了这个解决方案,以便不需要es6也能有一行代码的解决方案,并且在速度和效率方面仍然是相关的。 - Vaidd4
现在是2019年6月,但在最新的Google Chrome中已经不再是这样了,现在的差异几乎可以忽略不计(9:10)。 - Ivan Castellanos
这似乎是ES6变体中最有效的:http://jsben.ch/HvICq - Brian M. Hunt

4

使用Ramda

const swapKeysWithValues = 
  R.pipe(
    R.keys,
    R.reduce((obj, k) => R.assoc(source[k], k, obj), {})
  );

const result = swapKeysWithValues(source);

Ramda有R.invertR.invertObj,它们的作用是一样的。第一个函数将重复值放在子列表中。 - kidroca

2

现代JS解决方案:

const swapKeyValue = (object) =>
  Object.entries(object).reduce((swapped, [key, value]) => (
    { ...swapped, [value]: key }
  ), {});

Typescript:

type ValidKey = number | string;

const swapKeyValue = <K extends ValidKey, V extends ValidKey>(
  object: Record<K, V>
): Record<V, K> =>
  Object.entries(object)
    .reduce((swapped, [key, value]) => (
      { ...swapped, [value as ValidKey]: key }
    ), {} as Record<V, K>);

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