如何最有效地将对象的所有键转换为小写?

101

我想出了一个

function keysToLowerCase (obj) {
  var keys = Object.keys(obj);
  var n = keys.length;
  while (n--) {
    var key = keys[n]; // "cache" it, for less lookups to the array
    if (key !== key.toLowerCase()) { // might already be in its lower case version
        obj[key.toLowerCase()] = obj[key] // swap the value to a new lower case key
        delete obj[key] // delete the old key
    }
  }
  return (obj);
}

不过我不确定 v8 的行为,例如它是否会真正删除其他键,还是只会删除引用,导致垃圾收集器最终会给我带来麻烦?

另外,我创建了这些测试,希望您能在那里添加您的答案,以便我们可以看到它们的匹配情况。

编辑1: 根据测试结果,如果不检查键名是否已经转换成小写,速度会更快,但除了速度更快之外,忽略这一步骤并创建新的小写键名是否会导致更多混乱?垃圾回收器是否会满意?


2
你能否创建一个新对象而不是修改现有的对象?这样你就可以跳过所有的删除操作。 - Jason Orendorff
@JasonOrendorff 显然它更慢,每次都会创建一个不必要的对象,垃圾收集器对此也不会满意... - João Pinto Jerónimo
很不幸,你的测试是错误的。SETUP部分仅在每个时钟循环中运行一次。由于你在设置中声明了对象,因此只有第一个测试的第一次迭代实际上将对象键更改为小写字母,所有其他迭代都会得到一个键已经为小写字母的对象。在[10]版本中修复(http://jsperf.com/object-keys-to-lower-case/10)。(大约一小时前我发表了类似的评论,我说它只运行一次。我注意到那是错误的,所以我删除了我的评论并进行了更多测试) - some
@一些人 我认为JasonOrendorff在第7版中进行了更正,我添加了一个不缓存键名的函数,一切都非常接近(对我来说,[1014、1080、1008]):http://jsperf.com/object-keys-to-lower-case/11 - João Pinto Jerónimo
@JoãoPintoJerónimo key.toLowerCase() 被调用了两次,你可以将其存储在一个变量中,并在下一行中使用它。而且创建一个新对象,只是添加新键比:添加新键到原始对象+删除先前的键更慢,这很奇怪? - S.Serpooshan
显示剩余4条评论
24个回答

86

我想到的最快的方法是创建一个新对象:

var key, keys = Object.keys(obj);
var n = keys.length;
var newobj={}
while (n--) {
  key = keys[n];
  newobj[key.toLowerCase()] = obj[key];
}

我对v8的当前内部工作不够熟悉,无法给您一个明确的答案。几年前,我看过一个开发人员关于对象的视频,如果我没记错的话,它只会删除引用并让垃圾收集器处理。但那是几年前的事情了,所以即使当时是这样,现在也不需要这样。

以后会不会出问题?这取决于你在做什么,但很可能不会。创建短暂对象非常常见,因此代码已经针对此进行了优化。但是每个环境都有其限制,也许它会出问题。您必须使用实际数据进行测试。


1
这是最好的答案,但@JasonOrendorff也应该得到认可。 - João Pinto Jerónimo
我认为在 key = keys[n] 前面需要加上 var - Andrew Ferrier
@AndrewFerrier 正确。已修复。 - some
1
@MichaelWarner,没错,它只会触及你提供给它的对象中的键,而不会触及其子元素。这也是问题所在。看起来你想要进行深拷贝,但那是一个非常复杂的问题... - some
1
一个 for...in 循环可能更快且更易读。for (const key in obj) newObj[key.toLowerCase()] = obj[key]; - 3limin4t0r
显示剩余7条评论

56

使用Object.fromEntries(ES10)

使用新的Object.fromEntries方法,可以实现本地和不可变解决方案:


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

在该函数广泛可用之前,您可以使用以下polyfill自定义该函数:

Object.fromEntries = arr => Object.assign({}, ...Array.from(arr, ([k, v]) => ({[k]: v}) ));

一个好处是这种方法与Object.entries的作用相反,因此现在您可以在对象和数组表示之间来回切换。


40

我会像这样使用Lo-Dash.transform

var lowerObj = _.transform(obj, function (result, val, key) {
    result[key.toLowerCase()] = val;
});

3
现在你可以使用 Object.fromEntries 方法,在纯原生的 JavaScript 中实现同样的转换。 - Yves M.
@YvesM。IE11不支持 :( - Mosh Feu

40

个人而言,我会使用:

let objectKeysToLowerCase = function (origObj) {
    return Object.keys(origObj).reduce(function (newObj, key) {
        let val = origObj[key];
        let newVal = (typeof val === 'object') ? objectKeysToLowerCase(val) : val;
        newObj[key.toLowerCase()] = newVal;
        return newObj;
    }, {});
}

这个函数简洁且可以处理嵌套对象,返回一个新的对象而不是修改原始对象。

在我的有限本地测试中,这个函数比当前列出的其他递归解决方案(修复后)更快。我很想将其与其他解决方案进行基准测试,但jsperf目前无法访问。

它也是用ES5.1编写的,因此根据MDN上的文档,应该在FF 4+、Chrome 5+、IE 9.0+、Opera 12+、Safari 5+(也就是几乎所有浏览器)上运行。

纯JS赢得胜利。

我不会太担心所有内容的垃圾收集方面。一旦对旧对象的所有引用被销毁,它就会被GC回收,但是新对象仍将引用基本上所有它的属性,因此它们不会被回收。

任何函数、数组或RegExp都将通过引用进行“复制”。在内存方面,即使是字符串,也不会通过此过程被复制,因为大多数(全部?)现代JS引擎都使用string interning。我认为这只剩下了数字、布尔值和原始结构中形成的对象要被GC回收。

请注意,如果原始对象具有相同的小写表示形式的多个属性,则(所有实现的)此过程将丢失值。例如:

let myObj = { xx: 'There', xX: 'can be', Xx: 'only', XX: 'one!' };
console.log(myObj);
// { xx: 'There', xX: 'can be', Xx: 'only', XX: 'one!' }

let newObj = objectKeysToLowerCase(myObj);
console.log(newObj);
// { xx: 'one!' }

当然,有时这正是您想要的。

更新 2018-07-17

一些人指出原始函数在处理数组时效果不佳。这里是一个扩展的、更具弹性的版本。它可以正确地递归处理数组,并且如果初始值是数组或简单值,则可以正常工作:

let objectKeysToLowerCase = function (input) {
    if (typeof input !== 'object') return input;
    if (Array.isArray(input)) return input.map(objectKeysToLowerCase);
    return Object.keys(input).reduce(function (newObj, key) {
        let val = input[key];
        let newVal = (typeof val === 'object') && val !== null ? objectKeysToLowerCase(val) : val;
        newObj[key.toLowerCase()] = newVal;
        return newObj;
    }, {});
};

1
我建议采用这个解决方案,因为它是一个纯函数,不会改变原始对象。 - Eugene Fidelin
1
当一个键的值是一组值的数组类型时,此方法将无法正常工作。使用 typeof 方法将返回对象数组。@Molomby - VivekN
1
我喜欢你的解决方案,但如果值是一个数组,则它不起作用。建议修改您的条件以检查该值是否为数组类型或非对象类型。例如:let newVal = ( typeof val !== 'object') || ( Array.isArray( val) )? val: objectKeysToLowerCase(val); - Andy Lai
1
这个解决方案处理嵌套对象的能力应该被强调,不错的解决方案。 - Kubie
2
我正在使用你的解决方案,但它不能处理属性值为null的情况。 我已经修改了 if (typeof input !== 'object') return input;if (typeof input !== 'object' || input===null) return input; - Sven Tore
1
重申上面提到的,如果任何对象属性为null,这将抛出错误(typeof检查可以正确处理undefined)。在第一行检查中添加|| input == null以解决此问题。 - zcoop98

12

ES6版本:

Object.keys(source)
  .reduce((destination, key) => {
    destination[key.toLowerCase()] = source[key];
    return destination;
  }, {});

8
的方式很好,因为它本质上是一个单行代码。
import {
mapKeys
} from 'lodash/fp'

export function lowerCaseObjectKeys (value) {
return mapKeys(k => k.toLowerCase(), value)
}

1
参数混合 - Konstantin Vahrushev

7

在我的测试中,使用forEach似乎更快-并且删除新引用将使其成为垃圾回收的目标。

function keysToLowerCase(obj){
    Object.keys(obj).forEach(function (key) {
        var k = key.toLowerCase();

        if (k !== key) {
            obj[k] = obj[key];
            delete obj[key];
        }
    });
    return (obj);
}

var O={ONE:1,two:2,tHree:3,FOUR:4,Five:5,SIX:{a:1,b:2,c:3,D:4,E:5}}; keysToLowerCase(O);

/* 返回值:(对象) */

注:该代码将对象O中的所有键转换为小写字母。
{
    five:5,
    four:4,
    one:1,
    six:{
        a:1,
        b:2,
        c:3,
        D:4,
        E:5
    },
    three:3,
    two:2
}

我将你的函数添加到第9个版本中:http://jsperf.com/object-keys-to-lower-case/9 - João Pinto Jerónimo
1
还不如我的快... 反向 while 循环是 JavaScript 中最快的循环方式, 点击这个链接查看: http://jsperf.com/javascript-loop (反向 while 循环可能更快, 但结果可能显示另一个循环方式更快,可能是因为旧浏览器在运行前已执行过该循环方式) - João Pinto Jerónimo
不是一个纯函数 - 你正在改变参数。 - Eugene Fidelin

4

虽然ES10的Object.fromentries()方法可行

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

您可以使用以下片段来处理ES2015及以下版本的代码。
this.htmlWorkbookJSON = jsonData.map((element: Object) => {
    let entriesArray = Object.entries(element)
    const data = new Object()
    entriesArray.forEach(([key, value]) => {
      data[key.toLocaleLowerCase()] = value;
    })
    return data
  })

3
这里有一个最简单的解决方案,可以将所有JSON键转换为小写。
let o = {"Account_Number   ":"0102301", "customer_NaME":"name"}

o = Object.keys(o).reduce((c, k) => (c[k.toLowerCase().trim()] = o[k], c), {})

console.log(o)

3

简化回答

对于简单情况,您可以使用以下示例将所有键转换为小写:

Object.keys(example).forEach(key => {
  const value = example[key];
  delete example[key];
  example[key.toLowerCase()] = value;
});

您可以使用 toUpperCase() 将所有键转换为大写,而不是使用 toLowerCase()
Object.keys(example).forEach(key => {
  const value = example[key];
  delete example[key];
  example[key.toUpperCase()] = value;
});

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