classificar propriedades do objeto e JSON.stringify

93

Meu aplicativo tem uma grande variedade de objetos, que eu stringi e os salvei no disco. Infelizmente, quando os objetos no array são manipulados e, às vezes, substituídos, as propriedades dos objetos são listadas em ordens diferentes (sua ordem de criação?). Quando eu faço JSON.stringify () no array e o salvo, um diff mostra as propriedades sendo listadas em diferentes ordens, o que é irritante ao tentar mesclar os dados com ferramentas de diff e mesclagem.

Idealmente, gostaria de classificar as propriedades dos objetos em ordem alfabética antes de executar o stringify ou como parte da operação de stringify. Existe código para manipular os objetos de array em muitos lugares, e alterá-los para sempre criar propriedades em uma ordem explícita seria difícil.

Sugestões serão muito bem-vindas!

Um exemplo condensado:

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

A saída dessas duas chamadas de stringify são diferentes e aparecem em uma comparação de meus dados, mas meu aplicativo não se preocupa com a ordem das propriedades. Os objetos são construídos de muitas maneiras e lugares.

Innovine
fonte
Dê um exemplo do objeto que você está tentando sequenciar (saída JSON ou literal de objeto JS)
Bojangles
4
Não é garantido que as chaves de objeto em objetos tenham uma ordem fixa. Isso ocorre por design.
Passante
1
@rab, onde você descobriu isso? Nota: pode funcionar (e provavelmente funciona na maioria das vezes), mas eu quis dizer que não é garantido.
Dogbert
1
OP aqui. Não havia dependência na ordem das propriedades aqui, apenas uma questão de como evitar diffs nos dados serializados. Eventualmente, isso foi resolvido armazenando as propriedades em matrizes e classificando-as antes da serialização, como na resposta aceita abaixo.
Innovine

Respostas:

73

A abordagem mais simples, moderna e atualmente compatível com o navegador é simplesmente esta:

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

No entanto, esse método remove todos os objetos aninhados que não são referenciados e não se aplicam a objetos dentro de matrizes. Você também desejará nivelar o objeto de classificação se quiser algo como esta saída:

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

Você pode fazer isso com isto:

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

Para fazer isso programaticamente com algo que você mesmo pode ajustar, você precisa colocar os nomes das propriedades do objeto em uma matriz, então classificar a matriz em ordem alfabética e iterar por meio dessa matriz (que estará na ordem certa) e selecionar cada valor do objeto em esse pedido. "hasOwnProperty" também está marcada para que você definitivamente tenha apenas as propriedades do próprio objeto. Aqui está um exemplo:

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

Novamente, isso deve garantir que você itere em ordem alfabética.

Por fim, levando-o adiante da maneira mais simples, esta biblioteca permitirá que você classifique recursivamente qualquer JSON que passar para ela: 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));

Resultado

{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
Marksyzm
fonte
9
Isso é exatamente o que eu estava tentando evitar :) Mas obrigado, parece ser a solução correta, embora pesada.
Innovine
1
Existe um bug neste código. Você não pode fazer referência obj[i]no loop ifor porque é um número inteiro e os nomes das propriedades não são necessariamente (daí esta questão). Deve ser obj[arr[i]].
Andrew Ensley
1
Os retornos de chamada não são exclusivos do comportamento assíncrono.
markyzm
2
@Johann Ao usar um retorno de chamada aqui, ele se transforma iterateObjectAlphabeticallyem uma função reutilizável. Sem o retorno de chamada, o 'código de carga útil' teria que estar dentro da própria função. Eu acho que é uma solução muito elegante e que é usada em muitas bibliotecas e no próprio JS core. Por exemplo, Array.forEach .
Stijn de Witt
1
@marksyzm Está tudo bem, no meu caso eu tive que stringificar apenas uma seleção de campos. Portanto, sua resposta me colocou no caminho. Obrigado
Benj,
39

Acho que se você está no controle da geração JSON (e parece que está), então, para seus propósitos, esta pode ser uma boa solução: json-stable-stringify

No site do projeto:

JSON.stringify () determinístico com classificação personalizada para obter hashes determinísticos de resultados stringificados

Se o JSON produzido for determinístico, você poderá diferenciá-lo / mesclá-lo facilmente.

Stijn de Witt
fonte
Observe que esse repo está em mau estado: não é atualizado há 3 anos e tem problemas não resolvidos e solicitações de pull. Acho que é melhor olhar para algumas das respostas mais recentes.
Tom
3
@Tom Observe também que esse repo tem 5 milhões de downloads semanais (!). Em outras palavras, está sendo usado ativamente. Ter problemas abertos "não resolvidos" e / ou solicitações pull não significa muito. Só que o (s) autor (es) não se importam com essas questões ou não têm tempo, etc. Nem toda biblioteca precisa ser atualizada a cada dois meses. Na verdade, imho, as melhores bibliotecas recebem atualizações muito raramente. Quando algo é feito, está feito. Não estou dizendo que este projeto não tem problemas reais, mas se houver, indique-os. Não tenho nenhum envolvimento com este lib BTW.
Stijn de Witt
ES6 Acho que quebrou a capacidade de classificar objetos (não matrizes), conforme especificado. Eu preciso disso para edição do usuário. Mesmo que a ordem não importe para o computador. Faz para os usuários que o editam. Valeu a pena tentar.
TamusJRoyce
Além do que @Tom diz, também destacarei que este projeto não possui dependências de tempo de execução. O que significa que, se não houver problemas de segurança / manutenção neste repositório, ele provavelmente é bastante estável e seguro de usar. Além disso, acabei de testá-lo com o ES6 e parece funcionar bem (pelo menos no Firefox).
Phil
27

Você pode passar uma matriz classificada dos nomes das propriedades como o segundo argumento de JSON.stringify():

JSON.stringify(obj, Object.keys(obj).sort())
Christian d'Heureuse
fonte
1
Existe algum tipo de garantia documentada sobre esse comportamento?
rich remer
4
@richremer Sim, no padrão ECMAScript , o segundo parâmetro de JSON.stringify()é chamado replacere pode ser um array. Ele é usado para construir um PropertyListque é então usado SerializeJSONObject()para anexar as propriedades nessa ordem. Isso está documentado desde ECMAScript 5.
Christian d'Heureuse
14
Observe que se obj tiver objetos aninhados, as chaves aninhadas também devem estar presentes no segundo argumento; caso contrário, eles serão descartados! Contra-exemplo: > JSON.stringify({a: {c: 1, b: 2}}, ['a']) '{"a":{}}' uma maneira (hacky) de construir a lista de todas as chaves relevantes é usar JSON.stringify para atravessar o objeto: function orderedStringify(obj) { const allKeys = []; JSON.stringify(obj, (k, v) => { allKeys.push(k); return v; }); return JSON.stringify(obj, allKeys.sort()); } Exemplo:> orderedStringify({a: {c: 1, b: 2}}) '{"a":{"b":2,"c":1}}'
Saif Hakim
Object.keys () não é recursivo. Isso não funcionará para nenhum objeto que contenha matrizes ou objetos aninhados.
Jor
23

Não entendo por que a complexidade das melhores respostas atuais é necessária para obter todas as chaves recursivamente. A menos que seja necessário um desempenho perfeito, parece-me que podemos simplesmente ligar JSON.stringify()duas vezes, a primeira para obter todas as chaves e, na segunda vez, para realmente fazer o trabalho. Dessa forma, toda a complexidade da recursão é tratada por stringify, e sabemos que ele conhece suas coisas e como lidar com cada tipo de objeto:

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
fonte
isso é interessante, mas você realmente "trapaceou" usando stringify para mapear todas as chaves para um array. Essa matriz pode ser mais fácil, melhor e mais segura obtida por meio de Object.keys
Z. Khullah
4
Não, o ponto é que Object.keys()não é recursivo, portanto, se você tiver um objeto como {a: "A", b: {c: "C"} }e chamar Object.keys nele, você receberá apenas as chaves ae b, mas não c. JSON.stringifysabe tudo sobre recursão em cada tipo de objeto tratado pelo processo de serialização JSON, seja um objeto ou um array (e já implementa a lógica para reconhecer ambos), então deve tratar de tudo corretamente para nós.
Jor
1
Adoro esta solução, muito elegante e à prova de falhas ao que parece.
Markus
14

Atualização em 24/07/2018:

Esta versão classifica objetos aninhados e também suporta 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));
}

Caso de teste:

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

  });

Resposta preterida:

Uma versão concisa no ES2016. Crédito para @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), {}));
}
Aleung
fonte
Sintaxe incorreta. Deveria ser -function orderedJsonStringify(o) { return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {})); }
Ben
Isso, por enquanto, corrigiu meu problema com C # DataContractJsonSerializer e "__type" não sendo listados primeiro na string json. Obrigado.
Iogurte The Wise
2
Observe que isso, como a resposta de Christian, se comporta mal com objetos aninhados.
Daniel Griscom de
sortObjPropertiesByKey não está definido. Você quis dizer usar sortObjByKey?
Paul Lynch
Não parece que isso sortObjByKey()verifica as referências circulares, então tome cuidado, ele pode travar em um loop infinito dependendo dos dados de entrada. Exemplo: var objA = {}; var objB = {objA: objA}; objA.objB = objB;-> sortObjByKey(objA);->VM59:6 Uncaught RangeError: Maximum call stack size exceeded
Klesun
8

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
fonte
1
esta é provavelmente a melhor resposta que existe, Obrigado @David
acido
4

Esta é a mesma resposta 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"}"
Giridhar CR
fonte
3

Você pode adicionar uma toJSONfunção personalizada ao seu objeto, que pode ser usada para personalizar a saída. Dentro da função, adicionar propriedades atuais a um novo objeto em uma ordem específica deve preservar essa ordem quando for stringificada.

Veja aqui:

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

Não há nenhum método embutido para controlar a ordem porque os dados JSON devem ser acessados ​​por chaves.

Aqui está um jsfiddle com um pequeno exemplo:

http://jsfiddle.net/Eq2Yw/

Experimente comentar a toJSONfunção - a ordem das propriedades é invertida. Esteja ciente de que isso pode ser específico do navegador, ou seja, o pedido não é oficialmente suportado na especificação. Ele funciona na versão atual do Firefox, mas se você deseja uma solução 100% robusta, pode ser necessário escrever sua própria função de restrição.

Editar:

Veja também esta pergunta do SO sobre a saída não determinística do stringify, especialmente os detalhes de Daff sobre as diferenças do navegador:

Como verificar de forma determinística se um objeto JSON não foi modificado?

Dave R.
fonte
As propriedades de cada objeto são conhecidas (e principalmente strings), portanto, codificar as propriedades em meu próprio stringifier toJSON pode ser muito mais rápido do que classificar ...!
Innovine
O 'OrderJsonStringify' abaixo quase funcionou. Mas essa parece ser uma solução melhor para mim. Quando eu preciso obter '__type' do C # DataContractJsonSerializer para ser o primeiro item na string json. var json = JSON.stringify (myjsonobj, ['__type', 'id', 'text', 'somesubobj' etc .....]); um pouco de dor para ter uma lista de todas as chaves.
Yogurt The Wise,
3

Uma resposta recursiva e simplificada:

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
fonte
reorderdeve ser renomeado para sortObjecte isso não lida com matrizes
Jonathan Gawrych,
Obrigado por notar isso, e o problema do OP era recorrer a objetos, não arrays.
Jason Parham,
@JonathanGawrych Por que você classificaria arrays, eles deveriam ter um pedido. Um array [1,2,3] não é o mesmo que um array [2,3,1], mas um objeto não tem ordem de propriedade. O problema é que a ordem de repente importa após a estringificação - as strings para objetos 100% equivalentes podem ser diferentes. Infelizmente, o esforço para obter um stringify determinístico parece considerável, exemplo de pacote: github.com/substack/json-stable-stringify/blob/master/index.js
Mörre
1
@ Mörre - Acho que não fui específico o suficiente. Por "não lida com matrizes", quero dizer que as matrizes estão quebradas, não deixadas sem classificação. O problema é typeof arr === "object"que isso transformaria matrizes em objetos. Por exemplo, var obj = {foo: ["bar", "baz"]}seria transformado em{ "foo": { "0": "bar", "1": "baz"} }
Jonathan Gawrych
para corrigir o problema encontrado por @JonathanGawrych:if(obj.constructor.prototype !== Object.prototype)
ricka
3

Você pode classificar o objeto por nome de propriedade no EcmaScript 2015

function sortObjectByPropertyName(obj) {
    return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}
Mayki Nayki
fonte
Talvez um nome melhor fosse sortObjectPropertiesByName()ou mais simples sortPropertiesByName().
janeiro
3

Peguei a resposta de @Jason Parham e fiz algumas melhorias

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

Isso corrige o problema de arrays serem convertidos em objetos e também permite definir como classificar arrays.

Exemplo:

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

resultado:

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

A resposta aceita não funciona para mim para objetos aninhados por algum motivo. Isso me levou a codificar o meu próprio. Como é final de 2019 quando escrevo isto, existem mais algumas opções disponíveis no idioma.

Atualização: Eu acredito que a resposta de David Furlong é uma abordagem preferível à minha tentativa anterior, e eu a retirei. O meu conta com suporte para Object.entries (...), portanto, não há suporte para Internet Explorer.

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

-

MANTER ESTA VERSÃO ANTIGA PARA REFERÊNCIA HISTÓRICA

Descobri que uma matriz simples e plana de todas as chaves do objeto funcionará. Em quase todos os navegadores (não Edge ou Internet explorer, previsivelmente) e Node 12+, há uma solução bastante curta agora que Array.prototype.flatMap (...) está disponível. (O equivalente lodash também funcionaria.) Eu testei apenas no Safari, Chrome e Firefox, mas não vejo razão para que não funcione em qualquer outro lugar que suporte flatMap e JSON.stringify padrão (...) .

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

Com isso, você pode sequenciar sem dependências de terceiros e até mesmo passar seu próprio algoritmo de classificação que classifica os pares de entrada de valor-chave, para que você possa classificar por chave, carga útil ou uma combinação dos dois. Funciona para objetos aninhados, matrizes e qualquer combinação de tipos de dados simples e antigos.

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

Impressões:

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

Se você deve ter compatibilidade com mecanismos JavaScript mais antigos , pode usar essas versões um pouco mais detalhadas que emulam o comportamento flatMap. O cliente deve suportar pelo menos ES5, portanto, nenhum Internet Explorer 8 ou inferior.

Eles retornarão o mesmo resultado acima.

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
fonte
1

Funciona com lodash, objetos aninhados, qualquer valor de atributo de objeto:

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)

Ele depende do comportamento do Chrome e do Node que a primeira chave atribuída a um objeto é emitida primeiro por JSON.stringify.

AJP
fonte
2
Obrigado, mas eu tive esse problema há 4 anos, estou feliz em dizer que mudei um pouco desde então :)
Innovine
1
:) Bom ouvir isso. Embora eu tenha tido esse problema ontem e não tenha encontrado a resposta que estava procurando em sua (antiga, mas ainda relevante) pergunta :)
AJP
0

Experimentar:

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
fonte
Obrigado, mas aparentemente o pedido é indefinido e só funciona. Abordagem interessante embora!
Innovine
0

Eu fiz uma função para classificar o objeto, e com retorno de chamada .. que na verdade cria um novo objeto

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

e chamá-lo com objeto.

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

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

JSON.stringify( result )

que imprime {"value":"msio","os":"windows","name":"anu"}e para classificar com valor.

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

JSON.stringify( result )

quais impressões {"os":"windows","value":"msio","name":"anu"}

rab
fonte
1
Funciona bem, mas infelizmente não é recursivo.
Jonathan Gawrych,
0

Se os objetos na lista não tiverem as mesmas propriedades, gere um objeto mestre combinado antes de 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
fonte
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 );
}

// exemplo conforme abaixo. em cada camada, para objetos como {}, achatados em classificação chave. para matrizes, números ou strings, achatados como / com 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} "

santo
fonte
Explique por que usar uma biblioteca externa e, como disse @DeKaNszn, adicione algumas explicações e uma introdução. Isso seria apreciado.
Benj
esta função é copiada do meu projeto. em que eu uso o underscore.js.
Santo
0

Estendendo a resposta da AJP para lidar com matrizes:

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
fonte
0

Surpreso que ninguém tenha mencionado a isEqualfunção de Lodash .

Executa uma comparação profunda entre dois valores para determinar se eles são equivalentes.

Observação: este método oferece suporte à comparação de matrizes, buffers de matriz, booleanos, objetos de data, objetos de erro, mapas, números, objetos de objeto, expressões regulares, conjuntos, strings, símbolos e matrizes digitadas. Os objetos de objeto são comparados por suas próprias propriedades enumeráveis, não herdadas. Funções e nós DOM são comparados por igualdade estrita, ou seja, ===.

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

Com o problema original - as chaves sendo ordenadas de forma inconsistente - é uma ótima solução - e é claro que irá parar se encontrar um conflito em vez de serializar cegamente o objeto inteiro.

Para evitar a importação de toda a biblioteca, faça o seguinte:

import { isEqual } from "lodash-es";

Exemplo de bônus: você também pode usar isso com RxJS com este operador personalizado

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

Afinal, ele precisa de um Array que armazene em cache todas as chaves no objeto aninhado (caso contrário, ele omitirá as chaves não armazenadas em cache). A resposta mais antiga está simplesmente errada, porque o segundo argumento não se preocupa com a notação de ponto. Então, a resposta (usando Set) torna-se.

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
fonte
0

Aqui está uma abordagem de clone ... clone o objeto antes de converter para 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;
}

É muito novo, mas parece funcionar bem em arquivos package.json.

Corey Alix
fonte
-3

Existe um Array.sortmétodo que pode ser útil para você. Por exemplo:

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});
Egor4eg
fonte
1
Não desejo classificar a matriz, no entanto. Na verdade, a parte do array não é importante. Desejo classificar as propriedades de um objeto. A partir de alguns experimentos rápidos, parece que as propriedades são listadas na ordem em que são criadas. Uma maneira pode ser criar um novo objeto e copiar as propriedades em ordem alfabética, mas espero que haja algo mais fácil / rápido.
Innovine
1
@Innovine, mesmo isso não funcionaria, pois as chaves não têm garantia de serem ordenadas na hora de criação pela especificação.
Dogbert
Então, a questão é: como faço para stringificar meus objetos de forma que as chaves fiquem em uma ordem fixa ... não é impossível para mim fazer para (key in obj) e armazená-los em uma matriz temporária e classificá-la manualmente construir uma string .. mas espero desesperadamente que haja uma maneira mais fácil
Innovine
Não, é basicamente assim que se faz. Bem-vindo ao JavaScript.
marksyzm