sortowanie właściwości obiektów i JSON.stringify

103

Moja aplikacja ma dużą tablicę obiektów, które następnie modyfikuję i zapisuję na dysku. Niestety, gdy obiekty w tablicy są manipulowane, a czasami zastępowane, właściwości obiektów są wyświetlane w różnej kolejności (kolejność tworzenia?). Kiedy robię JSON.stringify () na tablicy i zapisuję ją, różnica pokazuje właściwości, które są wyświetlane w różnych kolejności, co jest denerwujące przy próbie dalszego scalania danych za pomocą narzędzi do porównywania i scalania.

Idealnie chciałbym posortować właściwości obiektów w kolejności alfabetycznej przed wykonaniem stringify lub jako część operacji stringify. Istnieje kod służący do manipulowania obiektami tablicowymi w wielu miejscach, a zmiana ich tak, aby zawsze tworzyły właściwości w jawnej kolejności, byłaby trudna.

Sugestie byłyby mile widziane!

Skrócony przykład:

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

Dane wyjściowe tych dwóch wywołań stringify są różne i pojawiają się w różnicy moich danych, ale moja aplikacja nie dba o kolejność właściwości. Obiekty są budowane na wiele sposobów i miejsc.

Innowacyjność
źródło
Podaj przykład obiektu, który próbujesz dodać do ciągu (dane wyjściowe JSON lub literał obiektu JS)
Bojangles
4
Nie ma gwarancji, że klucze obiektów w obiektach mają ustaloną kolejność. Jest to zgodne z projektem.
Przechodzień
1
@rab, gdzie się tego dowiedziałeś? Uwaga: może działać (i prawdopodobnie działa przez większość czasu), ale miałem na myśli, że nie ma gwarancji.
Dogbert
1
OP tutaj. Nie było tutaj zależności od kolejności właściwości, tylko pytanie, jak uniknąć różnic w serializowanych danych. Zostało to ostatecznie rozwiązane poprzez przechowywanie właściwości w tablicach i sortowanie ich przed serializacją, podobnie jak w zaakceptowanej odpowiedzi poniżej.
Innovine

Odpowiedzi:

81

Prostsze, nowoczesne i obecnie obsługiwane przez przeglądarkę podejście jest po prostu takie:

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

Jednak ta metoda usuwa wszystkie zagnieżdżone obiekty, do których nie istnieją odwołania, i nie ma zastosowania do obiektów w tablicach. Będziesz chciał spłaszczyć obiekt sortowania, jeśli chcesz czegoś takiego jak ten wynik:

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

Możesz to zrobić w ten sposób:

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

Aby zrobić to programowo za pomocą czegoś, co możesz dostosować samodzielnie, musisz wypchnąć nazwy właściwości obiektu do tablicy, a następnie posortować tablicę alfabetycznie i powtórzyć tę tablicę (która będzie we właściwej kolejności) i wybrać każdą wartość z obiektu w to zamówienie. Opcja „hasOwnProperty” jest również zaznaczona, więc na pewno masz tylko własne właściwości obiektu. Oto przykład:

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

Powinno to zagwarantować, że będziesz iterować w kolejności alfabetycznej.

Wreszcie, idąc dalej w najprostszy sposób, ta biblioteka pozwoli ci rekurencyjnie posortować każdy przekazany do niej JSON: 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));

Wynik

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
marksyzm
źródło
9
Właśnie tego starałem się uniknąć :) Ale dzięki, wydaje mi się, że to właściwe, choć ciężkie, rozwiązanie.
Innovine
1
W tym kodzie jest błąd. Nie możesz odwoływać się obj[i]w pętli for, ponieważ ijest to liczba całkowita, a nazwy właściwości niekoniecznie są (stąd to pytanie). Tak powinno być obj[arr[i]].
Andrew Ensley,
1
Wywołania zwrotne nie są wyłączne dla zachowania asynchronicznego.
marksyzm
2
@Johann Używając tutaj wywołania zwrotnego, zamienia iterateObjectAlphabeticallysię w funkcję wielokrotnego użytku. Bez wywołania zwrotnego „kod ładunku” musiałby znajdować się wewnątrz samej funkcji. Myślę, że jest to bardzo eleganckie rozwiązanie, które jest używane w wielu bibliotekach i samym rdzeniu JS. Np . Array.forEach .
Stijn de Witt
1
@marksyzm W porządku, w moim przypadku musiałem określić tylko wybrane pola. Twoja odpowiedź skierowała mnie więc na drogę. Dzięki
Benj,
39

Myślę, że jeśli kontrolujesz generowanie JSON (i brzmi tak, jakbyś to robił ), to dla twoich celów może być to dobre rozwiązanie: json-stable-stringify

Ze strony projektu:

deterministyczna JSON.stringify () z niestandardowym sortowaniem w celu uzyskania deterministycznych skrótów z stringowanych wyników

Jeśli wygenerowany JSON jest deterministyczny, powinieneś być w stanie łatwo go porównać / scalić.

Stijn de Witt
źródło
1
Zwróć uwagę, że repozytorium jest w złym stanie: nie było aktualizowane od 3 lat i zawiera nierozwiązane problemy i żądania ściągnięcia. Myślę, że lepiej przyjrzeć się niektórym nowszym odpowiedziom.
Tom
3
@Tom Pamiętaj również, że to repozytorium ma 5 milionów pobrań tygodniowo (!). Innymi słowy, jest aktywnie używany. Posiadanie „nierozwiązanych” otwartych problemów i / lub żądań ściągnięcia nie znaczy wiele. Tylko tyle, że autor (autorzy) nie przejmują się tymi problemami, nie mają czasu itp. Nie każda biblioteka musi być aktualizowana co kilka miesięcy. W rzeczywistości, imho, najlepsze biblioteki bardzo rzadko otrzymują aktualizacje. Kiedy coś jest zrobione, to się robi. Nie mówię, że ten projekt nie ma prawdziwych problemów, ale jeśli tak, wskaż je. Przy okazji nie mam żadnego związku z tym lib.
Stijn de Witt
Wydaje mi się, że ES6 zepsuł to, będąc w stanie sortować obiekty (inne niż tablice), jak określono. Potrzebuję tego do edycji użytkownika. Nawet jeśli porządek nie ma znaczenia dla komputera. Robi to użytkownikom, którzy go edytują. Warto było spróbować.
TamusJRoyce
1
Oprócz tego, co mówi @Tom, zwrócę również uwagę, że ten projekt nie ma żadnych zależności wykonawczych. Oznacza to, że jeśli w tym repozytorium nie ma problemów z bezpieczeństwem / konserwacją, prawdopodobnie jest całkiem stabilne i bezpieczne w użyciu. Po prostu przetestowałem to z ES6 i wydaje się, że działa dobrze (przynajmniej w Firefoksie).
Phil
29

Nie rozumiem, dlaczego potrzebna jest złożoność aktualnie najlepszych odpowiedzi, aby uzyskać wszystkie klucze rekurencyjnie. Wydaje mi się, że o ile nie jest potrzebne perfekcyjne wykonanie, możemy po prostu zadzwonić JSON.stringify()dwa razy, za pierwszym razem, aby zdobyć wszystkie klucze, a za drugim, aby naprawdę wykonać pracę. W ten sposób cała złożoność rekursji jest obsługiwana stringifyi wiemy, że zna jej treść i jak obsłużyć każdy typ obiektu:

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 );
}
Jor
źródło
to interesujące, ale tak naprawdę "oszukiwałeś" używając stringify do odwzorowania wszystkich kluczy na tablicę. Ta tablica może być łatwiejsza, lepsza i bezpieczniejsza, uzyskana za pomocą Object.keys
Bernardo Dal Corno
6
Nie, chodzi o to, że Object.keys()nie jest rekurencyjna, więc jeśli masz obiekt taki jak {a: "A", b: {c: "C"} }i wywołujesz na nim Object.keys, otrzymasz tylko klucze ai b, ale nie c. JSON.stringifywie wszystko o rekurencji w każdym typie obiektu obsługiwanym przez proces serializacji JSON, czy to obiekt, czy tablica (i już implementuje logikę rozpoznawania obu), więc powinien obsługiwać wszystko poprawnie dla nas.
Jor
1
Uwielbiam to rozwiązanie, wydaje się bardzo eleganckie i bezpieczne.
Markus
@Jor thanks, to działa dla mnie. Inne fragmenty, które wypróbowałem, zawiodły w taki czy inny sposób.
nevf
28

Możesz przekazać posortowaną tablicę nazw właściwości jako drugi argument JSON.stringify():

JSON.stringify(obj, Object.keys(obj).sort())
Christian d'Heureuse
źródło
1
Czy istnieje jakaś udokumentowana gwarancja tego zachowania?
bogate wspomnienie
4
@richremer Tak, w standardzie ECMAScript drugi parametr JSON.stringify()jest wywoływany replaceri może być tablicą. Służy do tworzenia elementu, PropertyListktóry jest następnie używany SerializeJSONObject()do dołączania właściwości w tej kolejności. Jest to udokumentowane od ECMAScript 5.
Christian d'Heureuse
15
Zwróć uwagę, że jeśli obiekt ma w sobie zagnieżdżone obiekty, to zagnieżdżone klucze muszą być również obecne w drugim argumencie; w przeciwnym razie zostaną upuszczone! Kontr-przykład: > JSON.stringify({a: {c: 1, b: 2}}, ['a']) '{"a":{}}' Jedna (hacky) sposób budowania wykaz wszystkich kluczy jest użycie JSON.stringify przechodzić obiektu na function orderedStringify(obj) { const allKeys = []; JSON.stringify(obj, (k, v) => { allKeys.push(k); return v; }); return JSON.stringify(obj, allKeys.sort()); } przykład:> orderedStringify({a: {c: 1, b: 2}}) '{"a":{"b":2,"c":1}}'
Saifa Hakim
1
Object.keys () nie jest cykliczna. To nie zadziała dla żadnego obiektu zawierającego zagnieżdżone tablice lub obiekty.
Jor
14

Aktualizacja 2018-7-24:

Ta wersja sortuje obiekty zagnieżdżone i obsługuje również tablice:

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

Przypadek testowy:

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

  });

Nieaktualna odpowiedź:

Zwięzła wersja w ES2016. Kredyt dla @codename, z https://stackoverflow.com/a/29622653/94148

function orderedJsonStringify(o) {
  return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}));
}
aleung
źródło
Niezupełnie poprawna składnia. Powinno być -function orderedJsonStringify(o) { return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {})); }
Ben
To na razie rozwiązało mój problem z C # DataContractJsonSerializer i „__type”, które nie są wyświetlane jako pierwsze w ciągu json. Dzięki.
Jogurt The Wise
2
Zauważ, że to, podobnie jak odpowiedź Christiana, źle się zachowuje z zagnieżdżonymi obiektami.
Daniel Griscom
Nie zdefiniowano sortObjPropertiesByKey. Czy chodziło Ci o użycie sortObjByKey?
Paul Lynch,
Nie wygląda na to, że sortObjByKey()sprawdza cykliczne odwołania, więc bądź ostrożny, może zawiesić się w nieskończonej pętli w zależności od danych wejściowych. Przykład: var objA = {}; var objB = {objA: objA}; objA.objB = objB;-> sortObjByKey(objA);->VM59:6 Uncaught RangeError: Maximum call stack size exceeded
Klesun
9

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;
David Furlong
źródło
2
jest to prawdopodobnie najlepszą odpowiedzią tam, Dzięki @David
acido
4

To jest to samo, co odpowiedź Satpala Singha

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"}"
Giridhar CR
źródło
3

Do obiektu można dodać funkcję niestandardową, toJSONktórej można użyć do dostosowania wyników. Wewnątrz funkcji dodanie bieżących właściwości do nowego obiektu w określonej kolejności powinno zachować tę kolejność, gdy zostanie poddana stringowaniu.

Spójrz tutaj:

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

Nie ma wbudowanej metody kontrolowania kolejności, ponieważ dane JSON mają być dostępne za pomocą kluczy.

Oto jsfiddle z małym przykładem:

http://jsfiddle.net/Eq2Yw/

Spróbuj wykomentować toJSONfunkcję - kolejność właściwości jest odwrócona. Należy pamiętać, że może to być specyficzne dla przeglądarki, tj. Zamawianie nie jest oficjalnie obsługiwane w specyfikacji. Działa w aktualnej wersji Firefoksa, ale jeśli potrzebujesz w 100% niezawodnego rozwiązania, być może będziesz musiał napisać własną funkcję stringifier.

Edytować:

Zobacz także to pytanie SO dotyczące niedeterministycznych danych wyjściowych stringify, w szczególności szczegóły Daffa dotyczące różnic w przeglądarkach:

Jak deterministycznie zweryfikować, czy obiekt JSON nie został zmodyfikowany?

Dave R.
źródło
Właściwości każdego obiektu są znane (i przeważnie są to ciągi znaków), więc zakodowanie ich na stałe w moim własnym łańcuchu znaków toJSON może być znacznie szybsze niż sortowanie ...!
Innovine
Poniższy „orderJsonStringify” prawie zadziałał. Ale wydaje mi się, że to lepsze rozwiązanie. Kiedy muszę uzyskać „__type” C # DataContractJsonSerializer jako pierwszy element w ciągu JSON. var json = JSON.stringify (myjsonobj, ['__type', 'id', 'text', 'somesubobj' etc .....]); trochę bólu mieć listę wszystkich kluczy.
Jogurt The Wise
3

Rekurencyjna i uproszczona odpowiedź:

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);
Jason Parham
źródło
reordernależy zmienić nazwę na, sortObjecta to nie obsługuje tablic
Jonathan Gawrych
Dziękuję, że to zauważyłeś, a problemem OP było używanie obiektów, a nie tablic.
Jason Parham
@JonathanGawrych Po co i tak sortować tablice, powinny one mieć kolejność. Tablica [1, 2, 3] to nie to samo, co tablica [2, 3, 1], ale obiekt nie ma porządku właściwości. Problem polega na tym, że kolejność nagle ma znaczenie po stringifikacji - ciągi znaków dla obiektów równoważnych w 100% mogą być różne. Niestety wysiłek uzyskania deterministycznego stringify wydaje się znaczny, przykładowy pakiet: github.com/substack/json-stable-stringify/blob/master/index.js
Mörre
1
@ Mörre - Myślę, że nie byłem wystarczająco szczegółowy. Mówiąc „nie obsługuje tablic”, miałem na myśli, że tablice są zepsute, a nie nieposortowane. Problem polega na typeof arr === "object"tym, że przekształciłby tablice w obiekty. Na przykład var obj = {foo: ["bar", "baz"]}będzie to{ "foo": { "0": "bar", "1": "baz"} }
Jonathan Gawrych
aby naprawić problem znaleziony przez @JonathanGawrych:if(obj.constructor.prototype !== Object.prototype)
ricka
3

Możesz sortować obiekty według nazwy właściwości w EcmaScript 2015

function sortObjectByPropertyName(obj) {
    return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}
Mayki Nayki
źródło
Może lepsza nazwa byłaby sortObjectPropertiesByName()lub prostsza sortPropertiesByName().
stycznia
3

Otrzymałem odpowiedź od @Jason Parham i wprowadziłem kilka ulepszeń

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

Rozwiązuje to problem konwertowania tablic na obiekty, a także umożliwia zdefiniowanie sposobu sortowania tablic.

Przykład:

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

wynik:

{content:[{id:1},{id:2},{id:3}]}
Piotr
źródło
2

Zaakceptowana odpowiedź z jakiegoś powodu nie działa w przypadku obiektów zagnieżdżonych. To doprowadziło mnie do stworzenia własnego kodu. Ponieważ piszę to pod koniec 2019 roku, w języku dostępnych jest kilka innych opcji.

Aktualizacja: Uważam, że odpowiedź Davida Furlonga jest lepszym podejściem do mojej wcześniejszej próby i zlekceważyłem to. Mój polega na obsłudze Object.entries (...), więc nie ma wsparcia dla Internet Explorera.

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

-

UTRZYMANIE TEJ STARSZEJ WERSJI DO ODNIESIENIA HISTORYCZNEGO

Odkryłem, że prosta, płaska tablica wszystkich kluczy w obiekcie będzie działać. W prawie wszystkich przeglądarkach (nie Edge ani Internet Explorer, jak można się spodziewać ) i Node 12+ istnieje dość krótkie rozwiązanie, teraz, gdy jest dostępne Array.prototype.flatMap (...) . (Odpowiednik lodash też by działał.) Testowałem tylko w Safari, Chrome i Firefox, ale nie widzę powodu, dla którego nie działałby nigdzie indziej, który obsługuje flatMap i standardowy 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);
}

Dzięki temu można określić ciągi bez zależności od innych firm, a nawet przekazać własny algorytm sortowania, który sortuje według par klucz-wartość, dzięki czemu można sortować według klucza, ładunku lub ich kombinacji. Działa dla zagnieżdżonych obiektów, tablic i dowolnej mieszanki zwykłych starych typów danych.

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

Wydruki:

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

Jeśli musisz mieć zgodność ze starszymi silnikami JavaScript , możesz użyć tych nieco bardziej szczegółowych wersji, które emulują zachowanie flatMap. Klient musi obsługiwać co najmniej ES5, więc nie ma przeglądarki Internet Explorer 8 lub starszej.

Zwrócą one taki sam wynik jak powyżej.

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);
}
Miles Elam
źródło
1

Działa z lodash, obiektami zagnieżdżonymi, dowolną wartością atrybutu obiektu:

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)

Opiera się na zachowaniu Chrome i Node, że pierwszy klucz przypisany do obiektu jest wyprowadzany jako pierwszy JSON.stringify.

AJP
źródło
2
Dzięki, ale miałem ten problem 4 lata temu, cieszę się, że mogę powiedzieć, że od tamtej pory trochę się
zmieniłem
1
:) Dobrze to słyszeć. Chociaż wczoraj miałem ten problem i nie znalazłem odpowiedzi, której szukałem pod Twoim (starym, ale wciąż aktualnym) pytaniem :)
AJP
0

Próbować:

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);
3y3
źródło
Dzięki, ale najwyraźniej kolejność jest nieokreślona i tak się składa, że ​​działa. Ciekawe podejście!
Innovine
0

Zrobiłem funkcję sortowania obiektu i wywołania zwrotnego .. która faktycznie tworzy nowy obiekt

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

i nazwij to obiektem.

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

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

JSON.stringify( result )

które drukuje {"value":"msio","os":"windows","name":"anu"}i do sortowania według wartości.

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

JSON.stringify( result )

który drukuje {"os":"windows","value":"msio","name":"anu"}

rab
źródło
1
Działa dobrze, ale niestety nie jest rekurencyjna.
Jonathan Gawrych
0

Jeśli obiekty na liście nie mają takich samych właściwości, wygeneruj połączony obiekt główny przed 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() );

Niels Gjeding Olsen
źródło
0
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 );
}

// przykład jak poniżej. w każdej warstwie, dla obiektów takich jak {}, spłaszczonych według klucza. dla tablic, liczb lub ciągów, spłaszczonych jak / z 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} "

święty
źródło
Proszę wyjaśnić, dlaczego używasz zewnętrznej biblioteki i, jak powiedział @DeKaNszn, dodaj wyjaśnienia i wprowadzenie. Byłoby to docenione.
Benj,
ta funkcja jest skopiowana z mojego projektu. w którym używam underscore.js.
saintthor
0

Rozszerzenie odpowiedzi AJP o obsługę tablic:

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;
}
gblff
źródło
0

Zaskoczony, nikt nie wspomniał o isEqualfunkcji lodash .

Wykonuje głębokie porównanie między dwiema wartościami, aby określić, czy są one równoważne.

Uwaga: Ta metoda obsługuje porównywanie tablic, buforów tablicowych, wartości logicznych, obiektów dat, obiektów błędów, map, liczb, obiektów obiektów, wyrażeń regularnych, zestawów, ciągów znaków, symboli i tablic o typie. Obiekty obiektów są porównywane według własnych, a nie dziedziczonych, wyliczalnych właściwości. Funkcje i węzły DOM są porównywane według ścisłej równości, tj. ===.

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

Przy pierwotnym problemie - niespójna kolejność kluczy - jest to świetne rozwiązanie - i oczywiście po prostu się zatrzyma, jeśli znajdzie konflikt, zamiast ślepo serializować cały obiekt.

Aby uniknąć importowania całej biblioteki, wykonaj następujące czynności:

import { isEqual } from "lodash-es";

Przykład bonusu: Możesz również użyć tego z RxJS z tym niestandardowym operatorem

export const distinctUntilEqualChanged = <T>(): MonoTypeOperatorFunction<T> => 
                                                pipe(distinctUntilChanged(isEqual));
Simon_Weaver
źródło
0

W końcu potrzebuje tablicy, która buforuje wszystkie klucze w zagnieżdżonym obiekcie (w przeciwnym razie pominie klucze niebuforowane). Najstarsza odpowiedź jest po prostu błędna, ponieważ drugi argument nie przejmuje się notacją kropkową. Tak więc odpowiedź (używając Set) brzmi.

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())
}
Polv
źródło
0

Oto podejście do klonowania ... klonuj obiekt przed konwersją do json:

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

Jeśli jest bardzo nowy, ale wydaje się działać na plikach package.json w porządku.

Corey Alix
źródło
-3

Jest Array.sortmetoda, która może ci pomóc. Na przykład:

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});
Egor4eg
źródło
1
Nie chcę jednak sortować tablicy. Właściwie część tablicowa nie jest ważna ... Chciałbym posortować właściwości obiektu .. z kilku szybkich eksperymentów wygląda na to, że właściwości są wymienione w kolejności, w jakiej zostały utworzone. Jednym ze sposobów może być utworzenie nowego obiektu i skopiowanie właściwości w kolejności alfabetycznej, ale mam nadzieję, że jest coś łatwiejszego / szybszego ..
Innovine
1
@Innovine, nawet to nie zadziała, ponieważ klucze nie są gwarantowane, że zostaną uporządkowane według czasu utworzenia zgodnie ze specyfikacją.
Dogbert
Wtedy pojawia się pytanie, w jaki sposób mam ciągnąć moje obiekty w taki sposób, aby klucze były w ustalonej kolejności .. nie jest niemożliwe, żebym zrobił dla (klucz w obj) i przechował je w tablicy tymczasowej i posortował to i ręcznie skonstruuj ciąg… ale mam nadzieję, że jest łatwiejszy sposób
Innovine
Nie, to właściwie sposób na zrobienie tego. Witamy w JavaScript.
marksyzm