JavaScript:对象重命名键

484

有没有一种聪明(即优化)的方式来重命名JavaScript对象中的键?

非优化的方法如下:

o[ new_key ] = o[ old_key ];
delete o[ old_key ];

21
“optimized”是什么意思?我认为这已经非常简洁了,没有内置的“重命名”操作。 - Pointy
12
这就是你能得到的全部。我会担心我的申请中的其他事情。另外,你正在处理对象而不是数组。在JavaScript中(严格意义上),没有关联数组。 - Felix Kling
5
@Jean Vincent: 它速度真的很慢吗? - Felix Kling
12
这是最优化和基础版。 - Livingston Samuel
6
除Safari以外,你的版本在所有现代浏览器中都是最快的。样例测试案例请访问 http://jsperf.com/livi-006。 - Livingston Samuel
显示剩余12条评论
36个回答

256

我认为最完整(且正确)的做法是:

if (old_key !== new_key) {
    Object.defineProperty(o, new_key,
        Object.getOwnPropertyDescriptor(o, old_key));
    delete o[old_key];
}

这种方法确保重命名的属性与原始属性表现完全相同

另外,对我来说,将其封装到函数/方法中并将其放入Object.prototype中的可能性与您的问题无关。


1
这是一个不错的仅限于ES5的解决方案,真正的优点是保留了所有属性。因此,这并不是“优化”的,但在ES5上肯定更准确。 - Jean Vincent
10
我认为这是正确的回答。之前我对使用下划线的写法持有些怀疑态度,但这可能是由于提问者的原因。 - Bent Cardan
3
如果这很重要的话,我认为这个ES5的使用方法是IE9及更高版本的解决方案。 - Scott Stafford
3
我建议添加一个验证,检查 o.old_key 是否存在。 将以下代码进行更改: if (old_key !== new_key) 变为: if (old_key !== new_key && o[old_key]) - jose
通常我会同意你的说法,但在这种情况下,我认为它已经足够清楚了,否则没有人会理解这个问题(使用了 o)。 - Valeriu Paloş
显示剩余4条评论

190

如果你要改变源对象,ES6可以在一行代码中实现。

delete Object.assign(o, {[newKey]: o[oldKey] })[oldKey];

如果您想要创建一个新的对象,则可以使用两行。

const newObject = {};
delete Object.assign(newObject, o, {[newKey]: o[oldKey] })[oldKey];

9
尝试:删除 Object.assign(o, {newKey: o.oldKey}).oldKey; 这对我很有效。 - Tim
7
为什么newKey周围有方括号[]?实际上即使没有这个也能运行。 - Soumya Kanti
4
好的 - 我已经得到了答案。这是ES6功能。 对于任何像我一样遇到这种情况的人,这里有一个好答案 - Soumya Kanti
6
如果你的变量名称更加详细,这个回答会更加强大。 - lowcrawler
5
注意:使用Object.assign只会进行浅拷贝。 - Johann
显示剩余7条评论

111

你可以将这段代码包装在一个函数中,并将其赋值给Object原型。可以使用流畅的接口风格使多个重命名操作更加连贯。

Object.prototype.renameProperty = function (oldName, newName) {
     // Do nothing if the names are the same
     if (oldName === newName) {
         return this;
     }
    // Check for the old property name to avoid a ReferenceError in strict mode.
    if (this.hasOwnProperty(oldName)) {
        this[newName] = this[oldName];
        delete this[oldName];
    }
    return this;
};

ECMAScript 5 特定

我希望语法不要这么复杂,但更多的控制确实很好。

Object.defineProperty(
    Object.prototype, 
    'renameProperty',
    {
        writable : false, // Cannot alter this property
        enumerable : false, // Will not show up in a for-in loop.
        configurable : false, // Cannot be deleted via the delete operator
        value : function (oldName, newName) {
            // Do nothing if the names are the same
            if (oldName === newName) {
                return this;
            }
            // Check for the old property name to 
            // avoid a ReferenceError in strict mode.
            if (this.hasOwnProperty(oldName)) {
                this[newName] = this[oldName];
                delete this[oldName];
            }
            return this;
        }
    }
);

4
@ChaosPandion 很抱歉,但我真的已经厌倦了bug密布的JS代码。我现在正在撰写一份指南(https://github.com/BonsaiDen/JavaScript-Garden),介绍所有怪癖(包括你刚刚解决的问题)。这可能让我变得有点发牢骚的倾向 ;)(现在已经取消了-1)。 - Ivo Wetzel
1
@Ivo - 你能解释一下为什么你认为扩展原型是不好的吗?原型就是为了扩展而生的! - ChaosPandion
5
@Ivo - 我同意这是有风险的,但如果我觉得它能导致更具表现力的代码,就没有什么可以阻止我扩展原型。虽然我来自于 ECMAScript 5 的思维模式,在那里你可以通过 Object.defineProperty 将属性标记为不可枚举。 - ChaosPandion
3
如果始终使用hasOwnProperty来检查属性并在for ... in循环中使用,那么扩展对象有什么关系呢? - David Tang
2
这段代码可以运行,但是需要注意的是,如果新的键名已经存在,它的值将会被覆盖:http://jsfiddle.net/ryurage/B7x8x/ - Brandon Minton
显示剩余5条评论

89

如果有人需要重命名一系列的属性:

function renameKeys(obj, newKeys) {
  const keyValues = Object.keys(obj).map(key => {
    const newKey = newKeys[key] || key;
    return { [newKey]: obj[key] };
  });
  return Object.assign({}, ...keyValues);
}

使用方法:

const obj = { a: "1", b: "2" };
const newKeys = { a: "A", c: "C" };
const renamedObj = renameKeys(obj, newKeys);
console.log(renamedObj);
// {A:"1", b:"2"}

2
这是一个很好的解决方案,非常适合我的用例。我需要转换一个API响应,该响应在国家名称前缀中添加了不必要的字符串。将对象转换为键数组,并通过每个键使用子字符串方法进行映射,然后返回具有新键和值索引的原始键的新对象。 - Akin Hwan
1
这很好,因为它不会删除旧的键。如果键名没有更改,则删除旧键将从对象中完全删除该键。在我的示例中,我正在将my_object_property转换为myObjectProperty,但是,如果我的属性只有一个单词,那么该键将被删除。+1 - blueprintchris

45

为每个键添加前缀:

const obj = {foo: 'bar'}

const altObj = Object.fromEntries(
  Object.entries(obj).map(([key, value]) => 
    // Modify key here
    [`x-${key}`, value]
  )
)

// altObj = {'x-foo': 'bar'}

6
在我看来,这绝对是最简单的答案,并且应该被接受。我用它来将所有键转换成小写并将空格替换为下划线。 - Dexygen
这是唯一一个保留键顺序的。 - PeiSong

33

我只想使用ES6(ES2015)的方式!

我们需要跟上时代的步伐!

const old_obj = {
    k1: `111`,
    k2: `222`,
    k3: `333`
};
console.log(`old_obj =\n`, old_obj);
// {k1: "111", k2: "222", k3: "333"}


/**
 * @author xgqfrms
 * @description ES6 ...spread & Destructuring Assignment
 */

const {
    k1: kA, 
    k2: kB, 
    k3: kC,
} = {...old_obj}

console.log(`kA = ${kA},`, `kB = ${kB},`, `kC = ${kC}\n`);
// kA = 111, kB = 222, kC = 333

const new_obj = Object.assign(
    {},
    {
        kA,
        kB,
        kC
    }
);

console.log(`new_obj =\n`, new_obj);
// {kA: "111", kB: "222", kC: "333"}

demo screen shortcut


1
当您想将一个格式不正确的对象转换为全新重构的对象时,发现这是一种更清晰的解决方案。 - Nagama Inamdar
7
不错,但我认为const {k1: kA, k2: kB, k3: kC,} = old_obj就足够了,不需要扩展(除非你想要old_obj的副本)。 - Björn
@Hans,是的,你说得对。但在这种情况下,我想要重命名一些键,所以我应该将它们展开。 - user8629798
2
这需要更多的代码,效率会降低,并且会创建一个新对象。重命名属性意味着改变对象。新不一定就是更好的。 - Jean Vincent

33
使用对象解构和展开运算符的变体:
```javascript const { prop1, prop2, ...rest } = obj; ```
其中`prop1`和`prop2`是从`obj`中解构出来的属性,而`rest`则包含了其余的属性。
const old_obj = {
    k1: `111`,
    k2: `222`,
    k3: `333`
};    

// destructuring, with renaming. The variable 'rest' will hold those values not assigned to kA, kB, or kC.
const {
    k1: kA, 
    k2: kB, 
    k3: kC,
    ...rest
} = old_obj;
    

// now create a new object, with the renamed properties kA, kB, kC; 
// spread the remaining original properties in the 'rest' variable
const newObj = {kA, kB, kC, ...rest};

对于一个键,它可以非常简单,例如:

const { k1: kA, ...rest } = old_obj;
const new_obj = { kA, ...rest }

您可能更喜欢一种更“传统”的风格:

const { k1, ...rest } = old_obj
const new_obj = { kA: k1, ...rest}

4
你能否解释一下这是在做什么,以及它是如何工作的? - Jon Adams
@JeffLowery 如果它是一个对象数组,我需要使用foreach吗?还是可以使用spread操作符? - user3808307
@user3808307 你可以在一个数组和数组中的对象上执行扩展操作。 - Jeff Lowery

32
如果您不想改变数据,考虑使用此函数...
renameProp = (oldProp, newProp, { [oldProp]: old, ...others }) => ({
  [newProp]: old,
  ...others
})

由Yazeed Bzadough提供的详细解释https://medium.com/front-end-hacking/immutably-rename-object-keys-in-javascript-5f6353c7b6dd

这是一个支持typescript的版本:

// These generics are inferred, do not pass them in.
export const renameKey = <
  OldKey extends keyof T,
  NewKey extends string,
  T extends Record<string, unknown>
>(
  oldKey: OldKey,
  newKey: NewKey extends keyof T ? never : NewKey,
  userObject: T
): Record<NewKey, T[OldKey]> & Omit<T, OldKey> => {
  const { [oldKey]: value, ...common } = userObject

  return {
    ...common,
    ...({ [newKey]: value } as Record<NewKey, T[OldKey]>)
  }
}

它将防止您覆盖现有的键或将其重命名为相同的内容


这是最好的解决方案。但需要对扩展运算符有一些了解。 - franco phong
绝对精彩。在第三个参数中使用第一个参数oldProp进行一些神奇的解构。 - Devin Rhode
“ts”示例没有帮助,缺少一个示例,在下面请参见我的评论 - Timo
TS版本不起作用... 我尝试使用renameKey("oldkey", "newkey", object)来激活,但它显示:类型为'string'的参数无法分配给类型为'never'的参数好像我的对象已经包含属性"string",因此第二个参数始终为"never"类型。 - undefined

26

这里大多数答案都没有保持JS对象键值对的顺序。如果您有一个屏幕上要修改的对象键值对形式的表单,重要的是要保留对象条目的顺序。

ES6遍历JS对象并使用具有修改后键名称的新键值对替换键值对的方法是:

let newWordsObject = {};

Object.keys(oldObject).forEach(key => {
  if (key === oldKey) {
    let newPair = { [newKey]: oldObject[oldKey] };
    newWordsObject = { ...newWordsObject, ...newPair }
  } else {
    newWordsObject = { ...newWordsObject, [key]: oldObject[key] }
  }
});

该解决方案通过在旧条目的位置添加新条目来保留条目的顺序。


非常感谢,我被其他所有答案弄糊涂了。这个答案简单明了,保持了顺序。正是我所需要的! - gignu
太简单了,太聪明了...我很惊讶它不是最佳答案...干得好! - Dobromir Kirov
抱歉,但在哈希映射中保留插入顺序并不是最佳实践。 - zergski

19
你可以尝试使用lodash _.mapKeys

var user = {
  name: "Andrew",
  id: 25,
  reported: false
};

var renamed = _.mapKeys(user, function(value, key) {
  return key + "_" + user.id;
});

console.log(renamed);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>


这个解决方案对我有用,但要注意始终返回一个“key”变量。例如:if(key === 'oldKey') { return 'newKey'; } return key; - TomoMiha

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