sorteer objecteigenschappen en JSON.stringify

Mijn toepassing heeft een groot aantal objecten, die ik stringereer en op de schijf bewaar. Wanneer de objecten in de array worden gemanipuleerd en soms worden vervangen, worden de eigenschappen van de objecten helaas in verschillende volgorde weergegeven (de volgorde waarin ze zijn gemaakt?). Wanneer ik JSON.stringify() op de array doe en het opsla, toont een diff dat de eigenschappen in verschillende volgorde worden weergegeven, wat vervelend is als ik probeer de gegevens verder samen te voegen met diff en samenvoeghulpmiddelen.

Idealiter zou ik de eigenschappen van de objecten in alfabetische volgorde willen sorteren voordat de stringify wordt uitgevoerd, of als onderdeel van de stringify-bewerking. Er is code voor het manipuleren van de array-objecten op veel plaatsen, en het zou moeilijk zijn om deze te wijzigen om altijd eigenschappen in een expliciete volgorde te creëren.

Suggesties zijn zeer welkom!

Een beknopt voorbeeld:

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

De uitvoer van deze twee stringify-aanroepen is verschillend en wordt weergegeven in een diff van mijn gegevens, maar mijn toepassing geeft niet om de volgorde van eigenschappen. De objecten zijn op vele manieren en plaatsen geconstrueerd.


Antwoord 1, autoriteit 100%

De eenvoudigere, modernere en momenteel door de browser ondersteunde benadering is eenvoudig deze:

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

Deze methode verwijdert echter alle geneste objecten waarnaar niet wordt verwezen en is niet van toepassing op objecten in arrays. U zult het sorteerobject ook willen afvlakken als u zoiets als deze uitvoer wilt:

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

Je kunt dat hiermee doen:

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;
};
var myFlattenedObj = flattenObject(sortMyObj);
JSON.stringify(myFlattenedObj, Object.keys(myFlattenedObj).sort());

Om het programmatisch te doen met iets dat je zelf kunt aanpassen, moet je de objecteigenschapsnamen in een array duwen, dan de array alfabetisch sorteren en door die array heen lopen (die in de juiste volgorde staat) en elke waarde selecteren uit het object in die volgorde. “hasOwnProperty” is ook aangevinkt, dus je hebt zeker alleen de eigen eigenschappen van het object. Hier is een voorbeeld:

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

Nogmaals, dit zou moeten garanderen dat je in alfabetische volgorde doorloopt.

Ten slotte, door het op de eenvoudigste manier verder te brengen, kunt u met deze bibliotheek recursief elke JSON sorteren die u erin doorgeeft: 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));

Uitvoer

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

Antwoord 2, autoriteit 47%

Ik begrijp niet waarom de complexiteit van de huidige beste antwoorden nodig is om alle sleutels recursief te krijgen. Tenzij perfecte prestaties nodig zijn, lijkt het mij dat we JSON.stringify()twee keer kunnen aanroepen, de eerste keer om alle sleutels te krijgen en de tweede keer om het werk echt te doen. Op die manier wordt alle recursie-complexiteit afgehandeld door stringify, en we weten dat het weet wat het is en hoe elk objecttype moet worden afgehandeld:

function JSONstringifyOrder( obj, space )
{
    var allKeys = [];
    var seen = {};
    JSON.stringify(obj, function (key, value) {
        if (!(key in seen)) {
            allKeys.push(key);
            seen[key] = null;
        }
        return value;
    });
    allKeys.sort();
    return JSON.stringify(obj, allKeys, space);
}

Antwoord 3, autoriteit 42%

Ik denk dat als je de JSON-generatie onder controle hebt (en het klinkt alsof je dat ook bent), dit voor jouw doeleinden een goede oplossing kan zijn: json-stable-stringify

Van de projectwebsite:

deterministische JSON.stringify() met aangepaste sortering om te krijgen
deterministische hashes van stringified resultaten

Als de geproduceerde JSON deterministisch is, zou je deze gemakkelijk moeten kunnen differentiëren/samenvoegen.


Antwoord 4, autoriteit 31%

U kunt een gesorteerde array van de eigenschapsnamen doorgeven als het tweede argument van JSON.stringify():

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

Antwoord 5, autoriteit 15%

Update 24-7-2018:

Deze versie sorteert geneste objecten en ondersteunt ook array:

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

Testcase:

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

Verouderd antwoord:

Een beknopte versie in ES2016.
Met dank aan @codename , van https://stackoverflow.com/a/29622653/94148

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

Antwoord 6, autoriteit 14%

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;

Antwoord 7, autoriteit 4%

Dit is hetzelfde als het antwoord van 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"}"

Antwoord 8, autoriteit 4%

Een recursief en vereenvoudigd antwoord:

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

Antwoord 9, autoriteit 4%

U kunt objecten sorteren op eigenschapsnaam in EcmaScript 2015

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

Antwoord 10, autoriteit 3%

U kunt een aangepaste functie toJSONaan uw object toevoegen die u kunt gebruiken om de uitvoer aan te passen. Binnen de functie zou het toevoegen van huidige eigenschappen aan een nieuw object in een specifieke volgorde die volgorde moeten behouden wanneer het wordt gesnared.

Zie hier:

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

Er is geen ingebouwde methode om de volgorde te regelen, omdat JSON-gegevens toegankelijk zijn via sleutels.

Hier is een jsfiddle met een klein voorbeeld:

http://jsfiddle.net/Eq2Yw/

Probeer commentaar te geven op de functie toJSON– de volgorde van de eigenschappen is omgekeerd. Houd er rekening mee dat dit browserspecifiek kan zijn, d.w.z. bestellen wordt niet officieel ondersteund in de specificatie. Het werkt in de huidige versie van Firefox, maar als je een 100% robuuste oplossing wilt, moet je misschien je eigen stringifier-functie schrijven.

Bewerken:

Zie ook deze SO-vraag over de niet-deterministische output van stringify, met name Daff’s details over browserverschillen:

Deterministisch verifiëren dat een JSON-object niet is gewijzigd ?


Antwoord 11, autoriteit 3%

Ik heb het antwoord van @Jason Parham overgenomen en enkele verbeteringen aangebracht

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

Dit lost het probleem op van arrays die worden geconverteerd naar objecten, en het stelt je ook in staat om te definiëren hoe arrays moeten worden gesorteerd.

Voorbeeld:

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

Uitgang:

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

Antwoord 12, Autoriteit 2%

Het geaccepteerde antwoord werkt om de een of andere reden niet voor mij voor geneste objecten. Dit leidde me om de mijne op te lossen. Omdat het eind 2019 is wanneer ik dit schrijf, zijn er nog een paar opties beschikbaar in de taal.

Update: ik geloof David Furlong’s antwoord is een voorkeursaanpak van mijn eerdere poging en ik heb dat van dat gebracht. De mijne is afhankelijk van ondersteuning voor object.Entries (…), dus geen ondersteuning voor internetverkenner.

function normalize(sortingFunction) {
  return function(key, value) {
    if (typeof value === 'object' && !Array.isArray(value)) {
      return Object
        .entries(value)
        .sort(sortingFunction || undefined)
        .reduce((acc, entry) => {
          acc[entry[0]] = entry[1];
          return acc;
        }, {});
    }
    return value;
  }
}
JSON.stringify(obj, normalize(), 2);

Houd deze oudere versie voor historische referentie

Ik vond dat een eenvoudige, platte reeks van alle sleutels in het object zal werken. In bijna alle browsers (niet rand of Internet Explorer, voorspelbaar) en knooppunt 12+ is er een vrij korte oplossing Nu dat array.prototype.flatmap (.. .) is beschikbaar. (Het equivalent van Lodash zou ook werken.) Ik heb alleen getest in Safari, Chrome en Firefox, maar ik zie geen reden waarom het nergens anders zou werken die flatmap en standaard json.stringify (…) .

function flattenEntries([key, value]) {
  return (typeof value !== 'object')
    ? [ [ key, value ] ]
    : [ [ key, value ], ...Object.entries(value).flatMap(flattenEntries) ];
}
function sortedStringify(obj, sorter, indent = 2) {
  const allEntries = Object.entries(obj).flatMap(flattenEntries);
  const sorted = allEntries.sort(sorter || undefined).map(entry => entry[0]);
  return JSON.stringify(obj, sorted, indent);
}

Hiermee kunt u zich staren met geen afhankelijkheden van 3e-partijen en zelfs in uw eigen sorteeralgoritme doorgeven die sorteert op de toetswaarde-ingangsparen, zodat u op sleutel, payload of een combinatie van de twee kunt sorteren. Werkt voor geneste objecten, arrays en elk mengsel van gewone oude gegevenstypen.

const obj = {
  "c": {
    "z": 4,
    "x": 3,
    "y": [
      2048,
      1999,
      {
        "x": false,
        "g": "help",
        "f": 5
      }
    ]
  },
  "a": 2,
  "b": 1
};
console.log(sortedStringify(obj, null, 2));

PRINTS:

{
  "a": 2,
  "b": 1,
  "c": {
    "x": 3,
    "y": [
      2048,
      1999,
      {
        "f": 5,
        "g": "help",
        "x": false
      }
    ],
    "z": 4
  }
}

Als u compatibiliteit moet hebben met oudere JavaScript-motoren , kunt u deze iets meer uitgebreide versies gebruiken die platmappengedrag emuleren. Cliënt moet ten minste ES5 ondersteunen, dus geen Internet Explorer 8 of hieronder.

Deze zullen hetzelfde resultaat als hierboven retourneren.

function flattenEntries([key, value]) {
  if (typeof value !== 'object') {
    return [ [ key, value ] ];
  }
  const nestedEntries = Object
    .entries(value)
    .map(flattenEntries)
    .reduce((acc, arr) => acc.concat(arr), []);
  nestedEntries.unshift([ key, value ]);
  return nestedEntries;
}
function sortedStringify(obj, sorter, indent = 2) {
  const sortedKeys = Object
    .entries(obj)
    .map(flattenEntries)
    .reduce((acc, arr) => acc.concat(arr), [])
    .sort(sorter || undefined)
    .map(entry => entry[0]);
  return JSON.stringify(obj, sortedKeys, indent);
}

Antwoord 13, autoriteit 2%

Ik heb zojuist een van de genoemde voorbeelden herschreven om het te gebruiken in stringify

const stringifySort = (key, value) => {
    if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
    return Object.keys(value).sort().reduce((obj, key) => (obj[key]=value[key], obj), {});
};
JSON.stringify({name:"X", os:"linux"}, stringifySort);

Antwoord 14

Werkt met lodash, geneste objecten, elke waarde van objectattribuut:

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)

Het is afhankelijk van het gedrag van Chrome en Node dat de eerste sleutel die aan een object is toegewezen, als eerste wordt uitgevoerd door JSON.stringify.


Antwoord 15

Probeer:

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

Antwoord 16

Ik heb een functie gemaakt om objecten te sorteren, en met callback .. die in feite een nieuw object maakt

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

en noem het met object .

var obj = {
    name : "anu",
    os : "windows",
    value : 'msio',
};
var result = sortObj( obj , function( a, b ){
    return a.key < b.key  ;    
});
JSON.stringify( result )

die {"value":"msio","os":"windows","name":"anu"}afdrukt, en voor het sorteren op waarde .

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

die {"os":"windows","value":"msio","name":"anu"}


Antwoord 17

Als objecten in de lijst niet dezelfde eigenschappen hebben, genereer dan een gecombineerd hoofdobject voordat u tekent:

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

Snippet uitvouwen


Antwoord 18

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

// voorbeeld zoals hieronder. in elke laag, voor objecten zoals {}, afgevlakt in sleutelsortering. voor arrays, getallen of strings, afgeplat zoals/met 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}”


Antwoord 19

Uitbreiding van het antwoord van AJP, om arrays te verwerken:

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

Antwoord 20

Het verbaast me dat niemand de isEqual-functie van lodash heeft genoemd.

Voert een diepgaande vergelijking uit tussen twee waarden om te bepalen of ze dat zijn
gelijkwaardig.

Opmerking: deze methode ondersteunt het vergelijken van arrays, arraybuffers, booleans,
datumobjecten, foutobjecten, kaarten, getallen, objectobjecten, regexes,
sets, strings, symbolen en getypte arrays. Objectobjecten worden vergeleken
door hun eigen, niet geërfde, opsombare eigenschappen. Functies en DOM
knooppunten worden vergeleken door strikte gelijkheid, d.w.z. ===.

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

Met het oorspronkelijke probleem – sleutels zijn inconsistent geordend – is het een geweldige oplossing – en het stopt natuurlijk gewoon als het een conflict vindt in plaats van blindelings het hele object te serialiseren.

Om te voorkomen dat u de hele bibliotheek importeert, doet u dit:

import { isEqual } from "lodash-es";

Bonusvoorbeeld:
Je kunt dit ook gebruiken met RxJS met deze aangepaste operator

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

Antwoord 21

Het heeft tenslotte een array nodig die alle sleutels in het geneste object in de cache opslaat (anders worden de niet-gecachete sleutels weggelaten.) Het oudste antwoord is gewoon fout, omdat het tweede argument niet om puntnotatie geeft. Dus het antwoord (met behulp van Set) wordt.

function stableStringify (obj) {
  const keys = new Set()
  const getAndSortKeys = (a) => {
    if (a) {
      if (typeof a === 'object' && a.toString() === '[object Object]') {
        Object.keys(a).map((k) => {
          keys.add(k)
          getAndSortKeys(a[k])
        })
      } else if (Array.isArray(a)) {
        a.map((el) => getAndSortKeys(el))
      }
    }
  }
  getAndSortKeys(obj)
  return JSON.stringify(obj, Array.from(keys).sort())
}

Antwoord 22

Hier is een kloonbenadering…kloon het object voordat het naar json wordt geconverteerd:

function sort(o: any): any {
    if (null === o) return o;
    if (undefined === o) return o;
    if (typeof o !== "object") return o;
    if (Array.isArray(o)) {
        return o.map((item) => sort(item));
    }
    const keys = Object.keys(o).sort();
    const result = <any>{};
    keys.forEach((k) => (result[k] = sort(o[k])));
    return result;
}

If is erg nieuw, maar lijkt prima te werken op package.json-bestanden.


Antwoord 23

Verwar niet met de objectbewaking van Chrome-debugger. Het toont gesorteerde sleutels in het object, ook al is het eigenlijk niet gesorteerd. U moet het object sorteren voordat u het tekent.


Antwoord 24

Er is een Array.sort-methode die nuttig voor u kan zijn. Bijvoorbeeld:

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});

Other episodes