web-dev-qa-db-fra.com

Renommer / remapper efficacement les clés d'objet javascript / json dans un tableau d'objets

J'ai des données JSON structurées comme ça. Supposons que cela soit interchangeable, via JSON.parse() :

[
    {
        "title": "pineapple",
        "uid": "ab982d34c98f"
    },
    {
        "title": "carrots",
        "uid": "6f12e6ba45ec"
    }
]

J'en ai besoin pour ressembler à ceci, remappant title en name et uid en id avec le résultat:

[
    {
        "name": "pineapple",
        "id": "ab982d34c98f"
    },
    {
        "name": "carrots",
        "id": "6f12e6ba45ec"
    }
]

La façon la plus évidente de le faire est la suivante:

str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots", "uid": "6f12e6ba45ec"}]';

var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
    arr[i].name = arr[i].title;
    arr[i].id = arr[i].uid;
    delete arr[i].title;
    delete arr[i].uid;
}
str = '[{"title": "pineapple","uid": "ab982d34c98f"},{"title": "carrots",               "uid": "6f12e6ba45ec"}]';

var arr = JSON.parse(str);
for (var i = 0; i<arr.length; i++) {
    arr[i].name = arr[i].title;
    arr[i].id = arr[i].uid;
    delete arr[i].title;
    delete arr[i].uid;
}

$('body').append("<pre>"+JSON.stringify(arr, undefined, 4)+"</pre>");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

... ou en utilisant quelque chose de plus complexe (quoique pas plus efficace) comme this .

Tout cela est beau et dandy, mais que faire s'il y avait 200 000 objets dans le tableau? C'est beaucoup de frais généraux de traitement.

Existe-t-il un moyen plus efficace de remapper un nom de clé? Peut-être sans parcourir l'ensemble du tableau d'objets? Si votre méthode est plus efficace, veuillez fournir des preuves/références.

21
brandonscript

Comme je l'ai déjà mentionné dans les commentaires, si vous pouvez faire certaines hypothèses sur les valeurs des objets, vous pouvez utiliser une expression régulière pour remplacer les clés, par exemple:

str = str.replace(/"title":/g, '"name":');

Ce n'est pas aussi "propre", mais cela pourrait faire le travail plus rapidement.


Si vous devez quand même analyser le JSON, une approche plus structurée serait de passer une fonction reviver à JSON.parse et vous pourrez peut-être éviter un passage supplémentaire sur le tableau. Cela dépend probablement de la façon dont le moteur implémente JSON.parse cependant (peut-être qu'ils analysent d'abord la chaîne entière puis font un deuxième passage avec la fonction reviver, auquel cas vous n'obtiendrez aucun avantage).

var arr = JSON.parse(str, function(prop, value) {
   switch(prop) {
     case "title":
        this.name = value;
        return;
     case "uid":
        this.id = value;
        return;
     default:
        return value;
   }
});

Benchmarks, en utilisant le script Node.js ci-dessous pour tester 3 fois:

1389822740739: Beginning regex rename test
1389822740761: Regex rename complete
// 22ms, 22ms, 21ms
1389822740762: Beginning parse and remap in for loop test
1389822740831: For loop remap complete
// 69ms, 68ms, 68ms
1389822740831: Beginning reviver function test
1389822740893: Reviver function complete
// 62ms, 61ms, 60ms

Il semble que l'expression régulière (dans ce cas) soit la plus efficace, mais soyez prudent lorsque vous essayez d'analyser JSON avec des expressions régulières .


Script de test, chargement de 100 230 lignes de l'exemple JSON de l'OP:

fs = require('fs');
fs.readFile('test.json', 'utf8', function (err, data) {
    if (err) {
        return console.log(err);
    }
    console.log(new Date().getTime() + ": Beginning regex rename test");
    var str = data.replace(/"title":/g, '"name":');
    str = str.replace(/"uid":/g, '"id":');
    JSON.parse(str);
    console.log(new Date().getTime() + ": Regex rename complete");
    console.log(new Date().getTime() + ": Beginning parse and remap in for loop test");
    var arr = JSON.parse(data);
    for (var i = 0; i < arr.length; i++) {
        arr[i].name = arr[i].title;
        arr[i].id = arr[i].uid;
        delete arr[i].title;
        delete arr[i].uid;
    }
    console.log(new Date().getTime() + ": For loop remap complete");
    console.log(new Date().getTime() + ": Beginning reviver function test");
    var arr = JSON.parse(data, function (prop, value) {
        switch (prop) {
            case "title":
                this.name = value;
                return;
            case "uid":
                this.id = value;
                return;
            default:
                return value;
        }
    });
    console.log(new Date().getTime() + ": Reviver function complete");
});
25
Felix Kling

J'ai posé cette question il y a longtemps, et depuis lors, je me suis habitué à utiliser Array.prototype.map () pour faire le travail, plus pour la stabilité et la propreté du code que pour les performances. Bien que ce ne soit certainement pas le plus performant, il a fière allure:

var repl = orig.map(function(obj) {
    return {
        name: obj.title,
        id: obj.uid
    }
})

Si vous avez besoin d'une fonction plus flexible (et compatible ES6), essayez:

let replaceKeyInObjectArray = (a, r) => a.map(o => 
    Object.keys(o).map((key) => ({ [r[key] || key] : o[key] })
).reduce((a, b) => Object.assign({}, a, b)))

par exemple.

const arr = [{ abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }, { abc: 1, def: 40, xyz: 50 }]
const replaceMap = { "abc": "yyj" }

replaceKeyInObjectArray(arr, replaceMap)

/*
[
    {
        "yyj": 1,
        "def": 40,
        "xyz": 50
    },
    {
        "yyj": 1,
        "def": 40,
        "xyz": 50
    },
    {
        "yyj": 1,
        "def": 40,
        "xyz": 50
    }
]
*/
12
brandonscript

Voici une autre version de la suggestion de l'OP d'utiliser map() pour plus de clarté (pas de performances).

var newItems = items.map(item => ({
    name: item.title,
    id: item.uid
}));

Cela utilise fonctions fléchées ES6 et les syntaxes de raccourci qui sont possibles lorsqu'il n'y a qu'un seul paramètre passé à la fonction et une seule instruction dans le corps de la fonction.

Selon votre historique avec les expressions lambda dans différentes langues, ce formulaire peut ou non résonner avec vous.

Soyez prudent lorsque vous retournez un objet littéral dans la syntaxe de raccourci de la fonction flèche comme celle-ci. N'oubliez pas les parens supplémentaires autour de l'objet littéral!

8
user2747594

Si vous voulez le rendre un peu plus réutilisable. C'est peut-être une approche décente.

function rekey(arr, lookup) {
        for (var i = 0; i < arr.length; i++) {
                var obj = arr[i];
                for (var fromKey in lookup) {
                        var toKey = lookup[fromKey];
                        var value = obj[fromKey];
                        if (value) {
                                obj[toKey] = value;
                                delete obj[fromKey];
                        }
                }
        }
        return arr;
}

var arr = [{ Apple: 'bar' }, { Apple: 'foo' }];
var converted = rekey(arr, { Apple: 'kung' });
console.log(converted);
2
luwes

Vous pouvez utiliser un package npm nommé node-data-transform .

Vos données :

const data = [
  {
    title: 'pineapple',
    uid: 'ab982d34c98f',
  },
  {
    title: 'carrots',
    uid: '6f12e6ba45ec',
  },
];

Votre cartographie:

const map = {
  item: {
    name: 'title',
    id: 'uid',
  },
};

Et utilisez le package:

const DataTransform = require("node-json-transform").DataTransform;
const dataTransform = DataTransform(data, map);
const result = dataTransform.transform();
console.log(result);

Résultat :

[
  {
    name: 'pineapple',
    id: 'ab982d34c98f'
  },
  {
    name: 'carrots',
    id: '6f12e6ba45ec'
  }
]

Ce n'est peut-être pas le meilleur moyen de performance, mais c'est assez élégant.

1
Youness Abbal
var jsonObj = [/*sample array in question*/   ]

Sur la base des différents benchmarks discutés ci-dessous, la solution la plus rapide est native pour:

var arr = [];
for(var i = 0, len = jsonObj .length; i < len; i++) {
  arr.Push( {"name": jsonObj[i].title, "id" : jsonObj[i].uid});
}

Je pense que sans utiliser de frameworks, ce sera l'option 2:

var arr = []
jsonObj.forEach(function(item) { arr.Push({"name": item.title, "id" : item.uid }); });

Il y a toujours un débat entre l'utilisation des fonctions navite et non navite. Si je me souviens bien lodash a soutenu qu'ils étaient plus rapides que le soulignement car ils utilisent des fonctions non natives pour les opérations clés.

Cependant, différents navigateurs produiront parfois des résultats très différents. J'ai toujours recherché la meilleure moyenne.

Pour les repères, vous pouvez jeter un œil à ceci:

http://jsperf.com/lo-dash-v1-1-1-vs-underscore-v1-4-4/8

1
Dalorzo

Utilisation d'ES6:

const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
  return arr.map(s => {
    return Object.keys(s).reduce((prev, next) => {
      if(next === oldField) { 
        prev[newField] = s[next]
      } else { 
        prev[next] = s[next] 
      }
      return prev
    }, {})
  })
}

Utilisation d'ES7:

const renameFieldInArrayOfObjects = (arr, oldField, newField) => {
  return arr.map(s => {
    return Object.keys(s).reduce((prev, next) => {
      return next === oldField
        ? {...prev, [newField]: s[next]}
        : {...prev, [next]: s[next]}
    }, {})
  })
}
1
ssomnoremac