web-dev-qa-db-fra.com

Quel est le meilleur moyen (le plus efficace) de transformer toutes les clés d'un objet en minuscule?

Je suis venu 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 la v8 se comportera avec cela, par exemple, supprimera-t-elle réellement les autres clés ou supprimera-t-elle uniquement les références et le ramasse-miettes me piquera-t-il plus tard?

De plus, j’ai créé ces tests , j’espère que vous pourrez ajouter votre réponse ici pour que nous puissions voir comment elles s’accordent.

EDIT 1: Apparemment, selon les tests, c'est plus rapide si on ne vérifie pas si la clé est déjà en minuscule, mais si on la met plus vite de côté, cela va-t-il créer plus de fouillis en l'ignorant et en créant simplement nouvelles touches minuscules? Les éboueurs seront-ils satisfaits de cela?

45

Le le plus rapide que je propose 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 connais pas suffisamment le fonctionnement interne actuel de la v8 pour vous donner une réponse définitive. Il y a quelques années, j'ai vu une vidéo dans laquelle les développeurs ont parlé d'objets, et IIRC Il supprimera uniquement les références et laissera le ramasse-miettes s'en charger. Mais c'était il y a des années, alors même si c'était comme ça à l'époque, ça n'a pas besoin d'être comme ça maintenant.

Est-ce que ça va te mordre 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 qu’il vous mordra. Vous devez tester avec les données réelles. 

Mise à jour 2017:

Ajout d'une fonction utilitaire permettant de créer une copie superficielle ou profonde d'un objet, en prenant en charge des références circulaires. Seulement brièvement testé sur le noeud.

/** @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;
}
48
some

Je voudrais utiliser Lo-Dash.transform comme ceci:

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

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, il est récurrent de gérer 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 les autres solutions récursives actuellement répertoriées (une fois corrigées). J'adorerais le comparer aux autres mais jsperf est en panne pour le moment (???).

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

Vanilla JS pour la victoire.

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

Toutes les fonctions, tableaux ou RegExp seront "copiés" d'un côté à l'autre. En termes de mémoire, même les chaînes ne seront pas dupliquées par ce processus puisque la plupart (tous?) Des moteurs JS modernes utilisent string interning . Je pense que cela ne laisse que les Nombres, les Booléens et les Objets qui ont formé la structure originale qui doivent être convertis.

Notez que (toutes les implémentations de) ce processus perdront des valeurs si l'original a plusieurs propriétés avec la même représentation en minuscule. 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.

Mise à jour 2018-07-17

Quelques personnes ont noté que la fonction d'origine ne fonctionnait pas bien avec les tableaux. Voici une version plus étendue et plus résiliente. Il se reproduit correctement dans les tableaux et fonctionne si la valeur initiale est un tableau ou une valeur simple:

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') ? objectKeysToLowerCase(val) : val;
        newObj[key.toLowerCase()] = newVal;
        return newObj;
    }, {});
};
17
Molomby

La façon loDash/fp, assez agréable car il s’agit essentiellement d’un one-liner

import {
mapKeys
} from 'lodash/fp'

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

Utiliser forEach semble être un peu plus rapide dans mes tests - et la référence d'origine a disparu, donc supprimer le nouveau le mettra à la portée du g.c.

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, deux: 2, trois: 4, cinq: 5, SIX: {a: 1, b: 2, c: 3, D: 4, E: 5}} ; keysToLowerCase (O);

/ * valeur renvoyée: (Object) * /

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

Version ES6:

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

J'y ai répondu de la même manière, mais j'utilise ES6 et TypeScript. La fonction toLowerCaseObject prend un tableau en tant que paramètre. Elle examine l'arborescence JSON de manière récursive et met chaque nœud en minuscule:

function toLowerCaseObject(items: any[]) {
        return items.map(x => {
            let lowerCased = {}
                for (let i in x) {
                    if (x.hasOwnProperty(i)) {
                        lowerCased[i.toLowerCase()] = x[i] instanceof Array ? toLowerCaseObject(x[i]) : x[i];
                    }
            }
            return lowerCased;
        });
    }
1
El.

Envisagez de réduire la casse une seule fois et de la stocker dans une variable lowKey var:

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

Réponse simplifiée

Pour des situations simples, vous pouvez utiliser l'exemple suivant pour convertir toutes les clés en minuscules:

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

Vous pouvez convertir toutes les touches en majuscule en utilisant toUpperCase() au lieu de toLowerCase() :

Object.keys(example).forEach(key => {
  const value = example[key];
  delete example[key];
  example[key.toUpperCase()] = value;
});
0
Grant Miller

Pour toutes les valeurs:

to_lower_case = function(obj) {
    for (var k in obj){
        if (typeof obj[k] == "object" && obj[k] !== null)
            to_lower_case(obj[k]);
        else if(typeof obj[k] == "string") {
            obj[k] = obj[k].toLowerCase();
        }
    }
    return obj;
}

Même peut être utilisé pour les clés avec des ajustements mineurs.

0
blockwala

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

Attention, il ne suit pas les références circulaires, vous pouvez donc vous retrouver avec une boucle infinie entraînant un débordement de pile.

0
kixorz

Ce n’est pas le moyen le plus propre, mais cela a fonctionné pour mon équipe, cela vaut donc la peine de le partager.

J'ai créé cette méthode car notre back-end utilise un langage qui ne respecte pas la casse. La base de données et le back-end vont générer différents cas clés. Pour nous, cela a fonctionné sans faille. Notez que nous envoyons des dates sous forme de chaînes et que nous n’envoyons pas de fonctions.

Nous l'avons réduit à cette seule ligne.

const toLowerCase = (data) => JSON.parse(JSON.stringify(data).replace(/"([^"]+)":/g, ($0, key) => '"' + key.toString().toLowerCase() + '":'))

Nous clonons l'objet en utilisant la méthode JSON.parse(JSON.stringify(obj)). Cela produit une version sous forme de chaîne de l'objet au format JSON. Lorsque l'objet se présente sous la forme d'une chaîne, vous pouvez utiliser regex car JSON est un format prévisible pour convertir toutes les clés.

Brisé il ressemble à ceci.

const toLowerCase = function (data) {
  return JSON.parse(JSON.stringify(data)
   .replace(/"([^"]+)":/g, ($0, key) => {
     return '"' + key.toString().toLowerCase() + '":'
   }))
}
0
Michael Warner