Quelle est la meilleure façon (le plus efficace) de transformer toutes les clés d'un objet en minuscules?

Je viens avec

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

Mais je ne suis pas sûr de savoir comment le v8 se comportera avec cela, par exemple, va-t-il vraiment supprimer les autres clés ou ne supprimera-t-il que les références et le collecteur de déchets me mordra plus tard?

De plus, j'ai créé ces tests , j'espère que vous pourriez ajouter votre réponse afin que nous puissions voir comment ils correspondent.

EDIT 1: Apparemment, selon les tests, il est plus rapide si nous ne vérifions pas si la clé est déjà en minuscule, mais, plus rapidement, cela créera-t-il plus d'encombrement en ignorant cela et en créant simplement de nouvelles clés minuscules? Est-ce que le collecteur d'ordures sera heureux avec cela?

Le plus rapide que je viens avec est si vous créez un nouvel objet:

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

Je ne suis pas assez familier avec le fonctionnement intérieur actuel de v8 pour vous donner une réponse définitive. Il y a quelques années, j'ai vu une vidéo où les développeurs parlaient d'objets, et IIRC ne supprime que les références et laisse le collecteur d'ordures s'en occuper. Mais c'était il y a des années, même si c'était comme ça alors, il n'a pas besoin d'être comme ça maintenant.

Est-ce que ça vous mordra plus tard? Cela dépend de ce que vous faites, mais probablement pas. Il est très courant de créer des objets de courte durée afin que le code soit optimisé pour le gérer. Mais chaque environnement a ses limites et peut-être vous mordra. Vous devez tester avec des données réelles.

Mise à jour 2017:

Ajout d'une fonction d'utilité qui peut faire une copie superficielle ou profonde d'un objet, en soutenant des références circulaires. Seulement testé brièvement sur le nœud.

 /** @summary objectKeysToLowerCase( input, deep, filter ) * returns a new object with all own keys converted to lower case. * The copy can be shallow (default) or deep. * * Circular references is supported during deep copy and the output will have * the same structure. * * By default only objects that have Object as constructor is copied. * It can be changed with the "filter"-function. * * NOTE: If an object has multiple keys that only differs in case, * only the value of the last seen key is saved. The order is usually * in the order that the keys where created. * Exaple : input = {aa:1, aA:2, Aa:3, AA:4}, output = {aa:4}; * * NOTE: To detect circular references, the list of objects already converted * is searched for every new object. If you have too many objects, it will * be slower and slower... * * @param {object} input * The source object * @param {boolean|number} deep * A shallow copy is made if "deep" is undefined, null, false or 0. * A deep copy is made if "deep" is true or a positive number. * The number specifies how many levels to copy. Infinity is a valid number. * This variable is used internally during deep copy. * @param {function} filter * A filter function(object) to filter objects that should be copied. * If it returns true, the copy is performed. * @returns {object} * */ function objectKeysToLowerCase( input, deep, filter ) { var idx, key, keys, last, output, self, type, value; self = objectKeysToLowerCase; type = typeof deep; // Convert "deep" to a number between 0 to Infinity or keep special object. if ( type === 'undefined' || deep === null || deep === 0 || deep === false ) { deep = 0; // Shallow copy } else if ( type === 'object' ) { if ( !( deep instanceof self ) ) { throw new TypeError( 'Expected "deep" to be a special object' ); } } else if ( deep === true ) { deep = Infinity; // Deep copy } else if ( type === 'number' ) { if ( isNaN(deep) || deep < 0 ) { throw new RangeError( 'Expected "deep" to be a positive number, got ' + deep ); } } else throw new TypeError( 'Expected "deep" to be a boolean, number or object, got "' + type + '"' ); // Check type of input, and throw if null or not an object. if ( input === null || typeof input !== 'object' ) { throw new TypeError( 'Expected "input" to be an object' ); } // Check type of filter type = typeof filter; if ( filter === null || type === 'undefined' || type === 'function' ) { filter = filter || null; } else { throw new TypeError( 'Expected "filter" to be a function' ); } keys = Object.keys(input); // Get own keys from object last = keys.length - 1; output = {}; // new object if (deep) { // only run the deep copy if needed. if (typeof deep === 'number') { // Create special object to be used during deep copy deep = Object.seal( Object.create( self.prototype, { input : { value : [] }, output : { value : [] }, level : { value : -1, writable:true }, max : { value : deep, writable:false } } ) ); } else { // Circle detection idx = deep.input.indexOf( input ); if ( ~idx ) { return deep.output[ idx ]; } } deep.level += 1; deep.input.push( input ); deep.output.push( output ); idx = last + 1; while ( idx-- ) { key = keys[ last - idx ]; // Using [last - idx] to preserve order. value = input[ key ]; if ( typeof value === 'object' && value && deep.level < deep.max ) { if ( filter ? filter(value) : value.constructor === Object ) { value = self( value, deep, filter ); } } output[ key.toLowerCase() ] = value; } deep.level -= 1; } else { // Simple shallow copy idx = last + 1; while ( idx-- ) { key = keys[ last - idx ]; // Using [last - idx] to preserve order. output[ key.toLowerCase() ] = input[ key ]; } } return output; } 

J'utiliserais Lo-Dash.transform comme ceci:

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

Personnellement, j'utiliserais:

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

C'est succinct, se reproduit pour traiter des objets imbriqués et renvoie un nouvel objet plutôt que de modifier l'original.

Dans mes tests locaux limités, cette fonction est plus rapide que l'autre solution récursive actuellement répertoriée (une fois qu'elle a été corrigée). J'aimerais le comparer aux autres, mais jsperf est en panne pour le moment (???).

Il est également écrit en ES5.1 donc, selon les docs sur MDN, devrait fonctionner dans FF 4+, Chrome 5+, IE 9.0+, Opera 12+, Safari 5+ (donc, à peu près tout).

  • Object.keys ()
  • Array. Prototype.reduce ()

Vanilla JS pour la victoire.

Je ne m'inquiéterais pas trop de l'aspect de la collecte des ordures de tout cela. Une fois que toutes les références à l'ancien objet sont détruites, ce sera GC, mais le nouvel objet fera toujours référence à toutes ses propriétés, de sorte qu'elles ne le feront pas.

Toutes les fonctions, les arrays ou RegExp seront "copiés" par référence. En termes de mémoire, même Strings ne sera pas dupliqué par ce processus puisque la plupart (tous?) Modernes JS moteurs utilisateur séquences d'internat . Je pense que cela laisse juste les nombres, les booléens et les Objets qui ont formé la structure d'origine restant GC'd.

Notez que (toutes les implémentations de) ce processus perdra des valeurs si l'original possède plusieurs propriétés avec la même représentation en minuscules. C'est à dire:

 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!' } 

Bien sûr, parfois, c'est exactement ce que vous voulez.

L'utilisation de EveryEach semble être un peu plus rapide dans mes tests – et la référence originale est allée, donc la suppression de la nouvelle la mettra en contact avec la Gc

 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, QUATRE: 4, Five: 5, SIX: {a: 1, b: 2, c: 3, D: 4, E: 5}}; KeysToLowerCase (O);

/ * Valeur retournée: (Objet) * /

 { five:5, four:4, one:1, six:{ a:1, b:2, c:3, D:4, E:5 }, three:3, two:2 } 

Envisagez d'abaisser le boîtier juste une fois, en le stockant dans une version lowKey :

 function keysToLowerCase (obj) { var keys = Object.keys(obj); var n = keys.length; var lowKey; while (n--) { var key = keys[n]; if (key === (lowKey = key.toLowerCase())) continue obj[lowKey] = obj[key] delete obj[key] } return (obj); } 

Voici ma version récursive basée sur l'un des exemples ci-dessus.

 //updated function var lowerObjKeys = function(obj) { Object.keys(obj).forEach(function(key) { var k = key.toLowerCase(); if (k != key) { var v = obj[key] obj[k] = v; delete obj[key]; if (typeof v == 'object') { lowerObjKeys(v); } } }); return obj; } //plumbing console = { _createConsole: function() { var pre = document.createElement('pre'); pre.setAttribute('id', 'console'); document.body.insertBefore(pre, document.body.firstChild); return pre; }, info: function(message) { var pre = document.getElementById("console") || console._createConsole(); pre.textContent += ['>', message, '\n'].join(' '); } }; //test case console.info(JSON.stringify(lowerObjKeys({ "StackOverflow": "blah", "Test": { "LULZ": "MEH" } }), true)); 

La manière LoDash / fp, assez agréable car c'est essentiellement une doublure

 import { mapKeys } from 'lodash/fp' export function lowerCaseObjectKeys (value) { return mapKeys(k => k.toLowerCase(), value) }