web-dev-qa-db-fra.com

trier les propriétés d'objet et JSON.stringify

Mon application possède un grand nombre d’objets, que je stringifie et enregistre sur le disque. Malheureusement, lorsque les objets du tableau sont manipulés et parfois remplacés, les propriétés des objets sont répertoriées dans des ordres différents (leur ordre de création?). Quand je fais JSON.stringify () sur le tableau et que je l'enregistre, un diff montre les propriétés répertoriées dans différents ordres, ce qui est gênant lorsque vous essayez de fusionner davantage les données avec des outils de diff et de fusion.

Idéalement, j'aimerais trier les propriétés des objets dans l'ordre alphabétique avant d'effectuer l'opération stringify ou dans le cadre de l'opération stringify. Il existe un code pour manipuler les objets du tableau à de nombreux endroits et il serait difficile de les modifier pour toujours créer des propriétés dans un ordre explicite. 

Des suggestions seraient les bienvenues!

Un exemple condensé:

obj = {}; obj.name="X"; obj.os="linux";
JSON.stringify(obj);
obj = {}; obj.os="linux"; obj.name="X";
JSON.stringify(obj);

La sortie de ces deux appels stringify est différente et apparaît dans un diff de mes données, mais mon application ne se soucie pas de l'ordre des propriétés .. les objets sont construits de nombreuses manières et endroits ..

60
Innovine

L’approche la plus simple, moderne et actuellement prise en charge par le navigateur est tout simplement la suivante:

JSON.stringify(sortMyObj, Object.keys(sortMyObj).sort());

Cependant, cette méthode supprime tous les objets imbriqués qui ne sont pas référencés et ne s'applique pas aux objets contenus dans des tableaux. Vous voudrez aussi aplatir l'objet de tri si vous voulez quelque chose comme cette sortie:

{"a":{"h":4,"z":3},"b":2,"c":1}

Vous pouvez le faire avec ceci:

var flattenObject = function(ob) {
    var toReturn = {};

    for (var i in ob) {
        if (!ob.hasOwnProperty(i)) continue;

        if ((typeof ob[i]) == 'object') {
            var flatObject = flattenObject(ob[i]);
            for (var x in flatObject) {
                if (!flatObject.hasOwnProperty(x)) continue;

                toReturn[i + '.' + x] = flatObject[x];
            }
        } else {
            toReturn[i] = ob[i];
        }
    }
    return toReturn;
};

JSON.stringify(sortMyObj, Object.keys(flattenObject(sortMyObj)).sort());

Pour le faire par programme avec quelque chose que vous pouvez modifier vous-même, vous devez insérer les noms des propriétés de l'objet dans un tableau, puis trier le tableau par ordre alphabétique, parcourir ce tableau (qui sera dans le bon ordre) et sélectionner chaque valeur de l'objet dans l'objet. cet ordre. "hasOwnProperty" est également coché, vous n'avez donc que les propriétés propres à l'objet. Voici un exemple:

var obj = {"a":1,"b":2,"c":3};

function iterateObjectAlphabetically(obj, callback) {
    var arr = [],
        i;

    for (i in obj) {
        if (obj.hasOwnProperty(i)) {
            arr.Push(i);
        }
    }

    arr.sort();

    for (i = 0; i < arr.length; i++) {
        var key = obj[arr[i]];
        //console.log( obj[arr[i]] ); //here is the sorted value
        //do what you want with the object property
        if (callback) {
            // callback returns arguments for value, key and original object
            callback(obj[arr[i]], arr[i], obj);
        }
    }
}

iterateObjectAlphabetically(obj, function(val, key, obj) {
    //do something here
});

Encore une fois, cela devrait garantir que vous parcourez la liste par ordre alphabétique. 

Enfin, pour aller plus loin, cette bibliothèque vous permettra récursivement de trier tous les JSON que vous y avez entrés: https://www.npmjs.com/package/json-stable-stringify

var stringify = require('json-stable-stringify');
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
console.log(stringify(obj));

Sortie

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
46
marksyzm

Je pense que si vous maîtrisez la génération JSON (et vous avez l’impression que vous l’êtes), cela pourrait être une bonne solution: json-stable-stringify

Sur le site du projet:

jSON.stringify () déterministe avec un tri personnalisé pour obtenir hachages déterministes à partir de résultats stringifiés

Si le JSON produit est déterministe, vous devriez pouvoir le différencier/le fusionner facilement.

29
Stijn de Witt

Vous pouvez passer un tableau trié des noms de propriété en tant que deuxième argument de JSON.stringify():

JSON.stringify(obj, Object.keys(obj).sort())
16

Mise à jour 2018-7-24: 

Cette version trie les objets imbriqués et supporte aussi les tableaux:

function sortObjByKey(value) {
  return (typeof value === 'object') ?
    (Array.isArray(value) ?
      value.map(sortObjByKey) :
      Object.keys(value).sort().reduce(
        (o, key) => {
          const v = value[key];
          o[key] = sortObjByKey(v);
          return o;
        }, {})
    ) :
    value;
}


function orderedJsonStringify(obj) {
  return JSON.stringify(sortObjByKey(obj));
}

Cas de test:

  describe('orderedJsonStringify', () => {
    it('make properties in order', () => {
      const obj = {
        name: 'foo',
        arr: [
          { x: 1, y: 2 },
          { y: 4, x: 3 },
        ],
        value: { y: 2, x: 1, },
      };
      expect(orderedJsonStringify(obj))
        .to.equal('{"arr":[{"x":1,"y":2},{"x":3,"y":4}],"name":"foo","value":{"x":1,"y":2}}');
    });

    it('support array', () => {
      const obj = [
        { x: 1, y: 2 },
        { y: 4, x: 3 },
      ];
      expect(orderedJsonStringify(obj))
        .to.equal('[{"x":1,"y":2},{"x":3,"y":4}]');
    });

  });

Réponse obsolète:

Une version concise dans ES2016 ............ crédit à @codename, de https://stackoverflow.com/a/29622653/94148

function orderedJsonStringify(o) {
  return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}));
}
13
aleung

C'est la même chose que la réponse de Satpal Singh

function stringifyJSON(obj){
    keys = [];
    if(obj){
        for(var key in obj){
            keys.Push(key);
        }
    }
    keys.sort();
    var tObj = {};
    var key;
    for(var index in keys){
        key = keys[index];
        tObj[ key ] = obj[ key ];
    }
    return JSON.stringify(tObj);
}

obj1 = {}; obj1.os="linux"; obj1.name="X";
stringifyJSON(obj1); //returns "{"name":"X","os":"linux"}"

obj2 = {}; obj2.name="X"; obj2.os="linux";
stringifyJSON(obj2); //returns "{"name":"X","os":"linux"}"
4
Giridhar C R

Une réponse récursive et simplifiée:

function sortObject(obj) {
    if(typeof obj !== 'object')
        return obj
    var temp = {};
    var keys = [];
    for(var key in obj)
        keys.Push(key);
    keys.sort();
    for(var index in keys)
        temp[keys[index]] = sortObject(obj[keys[index]]);       
    return temp;
}

var str = JSON.stringify(sortObject(obj), undefined, 4);
3
Jason Parham

J'ai pris la réponse de @Jason Parham et apporté quelques améliorations

function sortObject(obj, arraySorter) {
    if(typeof obj !== 'object')
        return obj
    if (Array.isArray(obj)) {
        if (arraySorter) {
            obj.sort(arraySorter);
        }
        for (var i = 0; i < obj.length; i++) {
            obj[i] = sortObject(obj[i], arraySorter);
        }
        return obj;
    }
    var temp = {};
    var keys = [];
    for(var key in obj)
        keys.Push(key);
    keys.sort();
    for(var index in keys)
        temp[keys[index]] = sortObject(obj[keys[index]], arraySorter);       
    return temp;
}

Cela corrige le problème de conversion des tableaux en objets et vous permet également de définir comment trier les tableaux.

Exemple:

var data = { content: [{id: 3}, {id: 1}, {id: 2}] };
sortObject(data, (i1, i2) => i1.id - i2.id)

sortie:

{content:[{id:1},{id:2},{id:3}]}
3
Peter

Vous pouvez ajouter une fonction toJSON personnalisée à votre objet que vous pouvez utiliser pour personnaliser le résultat. Dans la fonction, l'ajout de propriétés actuelles à un nouvel objet dans un ordre spécifique devrait préserver cet ordre lors de la chaîne.

Vois ici:

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify

Il n'y a pas de méthode intégrée pour contrôler les commandes car les données JSON doivent être accessibles via des clés.

Voici un jsfiddle avec un petit exemple:

http://jsfiddle.net/Eq2Yw/

Essayez de commenter la fonction toJSON - l'ordre des propriétés est inversé. Sachez que cela peut être spécifique au navigateur, c’est-à-dire que la commande n’est pas officiellement prise en charge dans la spécification. Cela fonctionne dans la version actuelle de Firefox, mais si vous voulez une solution robuste à 100%, vous devrez peut-être écrire votre propre fonction stringifier.

Modifier:

Voir également cette question SO concernant la sortie non déterministe de stringify, en particulier les détails de Daff concernant les différences entre les navigateurs:

Comment vérifier de manière déterministe qu'un objet JSON n'a pas été modifié?

3
Dave R.

Je ne comprends pas pourquoi la complexité des meilleures réponses actuelles est nécessaire pour obtenir toutes les clés de manière récursive (mais je ne peux pas les commenter en raison de limitations de réputation ...). À moins que des performances parfaites soient nécessaires, il me semble que nous pouvons appeler JSON.stringify() deux fois, la première fois pour obtenir toutes les clés, et la seconde fois, pour vraiment faire le travail. De cette façon, toute la complexité de la récursivité est gérée par stringify, et nous savons qu'il en connaît le contenu et sait comment gérer chaque type d'objet:

function JSONstringifyOrder( obj, space )
{
    var allKeys = [];
    JSON.stringify( obj, function( key, value ){ allKeys.Push( key ); return value; } )
    allKeys.sort();
    return JSON.stringify( obj, allKeys, space );
}
2
Jor

Fonctionne avec lodash, les objets imbriqués, toute valeur d'attribut d'objet:

function sort(myObj) {
  var sortedObj = {};
  Object.keys(myObj).sort().forEach(key => {
    sortedObj[key] = _.isPlainObject(myObj[key]) ? sort(myObj[key]) : myObj[key]
  })
  return sortedObj;
}
JSON.stringify(sort(yourObj), null, 2)

Selon le comportement de Chrome et du nœud, la première clé attribuée à un objet est générée en premier par JSON.stringify.

1
AJP

https://Gist.github.com/davidfurlong/463a83a33b70a3b6618e97ec9679e490

const replacer = (key, value) =>
    value instanceof Object && !(value instanceof Array) ? 
        Object.keys(value)
        .sort()
        .reduce((sorted, key) => {
            sorted[key] = value[key];
            return sorted 
        }, {}) :
        value;
1
David Furlong

Vous pouvez trier un objet par nom de propriété dans EcmaScript 2015.

function sortObjectByPropertyName(obj) {
    return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}
1
mike

Surpris, personne n'a mentionné la fonction isEqual de lodash.

Effectue une comparaison approfondie entre deux valeurs pour déterminer si elles sont équivalentes.

Remarque: cette méthode prend en charge la comparaison des tableaux, des tampons de tableau, des booléens, des objets de date, des objets d'erreur, des cartes, des nombres, des objets, des expressions rationnelles, des ensembles, des chaînes, des symboles et des tableaux typés. Les objets objet sont comparés par leurs propres propriétés énumérables, non héritées. Les fonctions et les nœuds DOM sont comparés par égalité stricte, c'est-à-dire ===.

https://lodash.com/docs/4.17.11#isEqual

Avec le problème initial - les clés étant ordonnées de manière incohérente - c'est une excellente solution - et bien sûr, cela s'arrêtera juste si un conflit survient au lieu de sérialiser aveuglément l'objet entier.

Pour éviter d'importer toute la bibliothèque, procédez comme suit:

import { isEqual } from "lodash-es";

Exemple de bonus: vous pouvez également utiliser cela avec RxJS avec cet opérateur personnalisé

export const distinctUntilEqualChanged = <T>(): MonoTypeOperatorFunction<T> => 
                                                pipe(distinctUntilChanged(isEqual));
0
Simon_Weaver

Essayer:

function obj(){
  this.name = '';
  this.os = '';
}

a = new obj();
a.name = 'X',
a.os = 'linux';
JSON.stringify(a);
b = new obj();
b.os = 'linux';
b.name = 'X',
JSON.stringify(b);
0
3y3

Si les objets de la liste n'ont pas les mêmes propriétés, générez un objet maître combiné avant stringify:

let arr=[ <object1>, <object2>, ... ]
let o = {}
for ( let i = 0; i < arr.length; i++ ) {
  Object.assign( o, arr[i] );
}
JSON.stringify( arr, Object.keys( o ).sort() );

0

J'ai créé une fonction pour trier un objet, et avec callback .. qui crée en fait un nouvel objet

function sortObj( obj , callback ) {

    var r = [] ;

    for ( var i in obj ){
        if ( obj.hasOwnProperty( i ) ) {
             r.Push( { key: i , value : obj[i] } );
        }
    }

    return r.sort( callback ).reduce( function( obj , n ){
        obj[ n.key ] = n.value ;
        return obj;
    },{});
}

et appelez-le avec objet.

var obj = {
    name : "anu",
    os : "windows",
    value : 'msio',
};

var result = sortObj( obj , function( a, b ){
    return a.key < b.key  ;    
});

JSON.stringify( result )

qui imprime {"value":"msio","os":"windows","name":"anu"}, et pour le tri avec valeur.

var result = sortObj( obj , function( a, b ){
    return a.value < b.value  ;    
});

JSON.stringify( result )

qui imprime {"os":"windows","value":"msio","name":"anu"}

0
rab

Étendre la réponse d'AJP pour gérer les tableaux:

function sort(myObj) {
    var sortedObj = {};
    Object.keys(myObj).sort().forEach(key => {
        sortedObj[key] = _.isPlainObject(myObj[key]) ? sort(myObj[key]) : _.isArray(myObj[key])? myObj[key].map(sort) : myObj[key]
    })
    return sortedObj;
}
0
gblff
function FlatternInSort( obj ) {
    if( typeof obj === 'object' )
    {
        if( obj.constructor === Object )
        {       //here use underscore.js
            let PaireStr = _( obj ).chain().pairs().sortBy( p => p[0] ).map( p => p.map( FlatternInSort ).join( ':' )).value().join( ',' );
            return '{' + PaireStr + '}';
        }
        return '[' + obj.map( FlatternInSort ).join( ',' ) + ']';
    }
    return JSON.stringify( obj );
}

// exemple comme ci-dessous. dans chaque couche, pour des objets comme {}, aplatis dans le type de clé. pour les tableaux, les nombres ou les chaînes, aplatis comme/avec JSON.stringify.

FlatternInSort ({c: 9, b: {y: 4, z: 2, e: 9}, F: 4, a: [{j: 8, h: 3}, {a: 3, b: 7}] })

"{" F ": 4," a ": [{" h ": 3," j ": 8}, {" a ": 3," b ": 7}]," b ": {" e " : 9, "y": 4, "z": 2}, "c": 9} "

0
saintthor