Подтвердить что ты не робот

Свойства объекта сортировки и JSON.stringify

Мое приложение имеет большой массив объектов, которые я преобразую в строку и сохраняю их на диск. К сожалению, когда объекты в массиве обрабатываются, а иногда и заменяются, свойства объектов перечисляются в разных порядках (порядок их создания?). Когда я выполняю JSON.stringify() для массива и сохраняю его, diff показывает свойства, перечисленные в разных порядках, что раздражает при попытке дальнейшего объединения данных с помощью инструментов сравнения и слияния.

В идеале я хотел бы отсортировать свойства объектов в алфавитном порядке перед выполнением stringify или как часть операции stringify. Существует код для манипулирования объектами массива во многих местах, и изменить их так, чтобы всегда создавать свойства в явном порядке, было бы сложно.

Предложения будут приветствоваться!

Сокращенный пример:

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

Вывод этих двух вызовов stringify различен и отображается в разных моих данных, но мое приложение не заботится о порядке следования свойств. Объекты строятся разными способами и местами.

4b9b3361

Ответ 1

Более простой, современный и в настоящее время поддерживаемый браузером подход - это просто:

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

Однако этот метод удаляет любые вложенные объекты, на которые не ссылаются, и не применяется к объектам внутри массивов. Вы также захотите сгладить объект сортировки, если хотите получить такой результат:

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

Вы можете сделать это с помощью этого:

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

Чтобы сделать это программно с помощью чего-то, что вы можете настроить, вам нужно нажать имена свойств объекта в массив, затем отсортировать массив по алфавиту и выполнить итерацию по этому массиву (который будет в правильном порядке) и выбрать каждое значение из объекта в этот заказ. "hasOwnProperty" также проверяется, поэтому у вас определенно есть только собственные свойства объекта. Вот пример:

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

Опять же, это должно гарантировать, что вы выполните итерацию в алфавитном порядке.

Наконец, взяв его более простейшим способом, эта библиотека рекурсивно позволит вам отсортировать любой 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));

Выход

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

Ответ 2

Я думаю, что если вы контролируете генерацию JSON (и это похоже на вас), то для ваших целей это может быть хорошим решением: json-stable-stringify

На веб-сайте проекта:

детерминированный JSON.stringify() с пользовательской сортировкой для получения детерминированные хеши из строгих результатов

Если произведенный JSON детерминирован, вы должны иметь возможность легко различать/объединять его.

Ответ 3

Вы можете передать отсортированный массив имен свойств в качестве второго аргумента JSON.stringify():

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

Ответ 4

Обновление 2018-7-24:

Эта версия сортирует вложенные объекты и поддерживает массив:

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

Прецедент:

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

  });

Устаревший ответ:

Краткая версия в ES2016. Подпишитесь на @codename, с fooobar.com/questions/2686/...

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

Ответ 5

Я не понимаю, зачем нужна сложность текущих лучших ответов, чтобы получить все ключи рекурсивно. Если не требуется идеальная производительность, мне кажется, что мы можем просто вызвать JSON.stringify() дважды, первый раз, чтобы получить все ключи, и второй раз, чтобы действительно сделать эту работу. Таким образом, вся сложность рекурсии обрабатывается с помощью stringify, и мы знаем, что она знает свои вещи и как обрабатывать каждый тип объекта:

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

Ответ 6

Это так же, как ответ Сатпал Сингх

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

Ответ 7

Вы можете добавить пользовательскую функцию toJSON к своему объекту, которую вы можете использовать для настройки вывода. Внутри функции добавление текущих свойств к новому объекту в определенном порядке должно сохранять этот порядок при стригировании.

Смотрите здесь:

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

Нет встроенного метода управления заказами, поскольку данные JSON предназначены для доступа к ключам.

Здесь jsfiddle с небольшим примером:

http://jsfiddle.net/Eq2Yw/

Попробуйте комментировать функцию toJSON - порядок свойств отменяется. Имейте в виду, что это может быть специфично для браузера, т.е. Заказы официально не поддерживаются в спецификации. Он работает в текущей версии Firefox, но если вы хотите 100% -ное надежное решение, возможно, вам придется написать свою собственную функцию стробирования.

Edit:

Также см. этот SO-вопрос, касающийся строкой недетерминированного вывода, особенно данные Daff о различиях браузера:

Как детерминистически проверить, что объект JSON не был изменен?

Ответ 8

Рекурсивный и упрощенный ответ:

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

Ответ 9

Я взял ответ от Джейсона Пархама и сделал некоторые улучшения

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

Это устраняет проблему преобразования массивов в объекты, а также позволяет определить способ сортировки массивов.

Пример:

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

выход:

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

Ответ 10

Вы можете отсортировать объект по имени свойства в EcmaScript 2015

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

Ответ 12

Работает с lodash, вложенными объектами, любым значением атрибута объекта:

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)

Он полагается на поведение Chrome и Node, что первый ключ, назначенный объекту, выводится сначала JSON.stringify.

Ответ 13

Try:

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

Ответ 14

Я сделал функцию для сортировки объекта и с обратным вызовом.. который фактически создает новый объект

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

и назовите его с помощью объекта.

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

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

JSON.stringify( result )

который печатает {"value":"msio","os":"windows","name":"anu"}, и для сортировки со значением.

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

JSON.stringify( result )

который печатает {"os":"windows","value":"msio","name":"anu"}

Ответ 15

Если объекты в списке не имеют одинаковых свойств, сгенерируйте объединенный главный объект перед строкой:

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

Ответ 16

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

//пример, как показано ниже. в каждом слое, для объектов типа {}, сглаженных в сортировке ключей. для массивов, чисел или строк, сглаженных как/с 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," а ": [{" ч ": 3," J ": 8}, {" а ": 3," б ": 7}]," б ": {" е ": 9," у ": 4," г ": 2}," С ": 9}"

Ответ 17

Расширение ответа AJP для обработки массивов:

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

Ответ 18

Удивлен, никто не упомянул функцию lodash isEqual.

Выполняет глубокое сравнение между двумя значениями, чтобы определить, являются ли они эквивалент.

Примечание. Этот метод поддерживает сравнение массивов, буферов массивов, логических значений, объекты даты, объекты ошибок, карты, числа, объекты объектов, регулярные выражения, наборы, строки, символы и типизированные массивы. Объектные объекты сравниваются своими, не наследуемыми, перечисляемыми свойствами. Функции и ДОМ узлы сравниваются по строгому равенству, т.е. ===.

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

С исходной проблемой - непоследовательным упорядочением ключей - это отличное решение - и, конечно, он просто остановится, если обнаружит конфликт, вместо слепой сериализации всего объекта.

Чтобы избежать импорта всей библиотеки, вы делаете это:

import { isEqual } from "lodash-es";

Бонусный пример: Вы также можете использовать это с RxJS с этим пользовательским оператором

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

Ответ 19

Принятый ответ почему-то не работает для вложенных объектов. Это привело меня к написанию своего кода. Начиная с конца 2019 года, когда я пишу это, на этом языке доступно еще несколько вариантов.

Обновление: я верю, что ответ дэвида Фарлонга является предпочтительным подходом к моей более ранней попытке, и я обманываю это. Мой опирается на поддержку Object.entries(...), поэтому нет поддержки 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);

-

СОХРАНЯЯ ЭТУ СТАРУЮ ВЕРСИЮ ДЛЯ ИСТОРИЧЕСКОЙ ССЫЛКИ

Я обнаружил, что будет работать простой плоский массив всех ключей в объекте. Почти во всех браузерах (не Edge или Internet Explorer, как ожидается) и Node 12+ есть довольно короткое решение, теперь Array.prototype.flatMap(...) доступно. (Аналог lodash тоже подойдет.) Я тестировал только в Safari, Chrome и Firefox, но не вижу причин, по которым он не будет работать где-либо еще, который поддерживает flatMap и стандартный 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);
}

При этом вы можете выполнять строковое преобразование без сторонних зависимостей и даже передавать свой собственный алгоритм сортировки, который сортирует по парам ввода ключ-значение, так что вы можете сортировать по ключу, полезной нагрузке или их комбинации. Работает для вложенных объектов, массивов и любой смеси простых старых типов данных.

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

Печать:

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

Если вам нужна совместимость со старыми механизмами JavaScript, вы можете использовать эти несколько более подробные версии, имитирующие поведение flatMap. Клиент должен поддерживать по крайней мере ES5, поэтому Internet Explorer 8 и ниже не должен быть.

Они вернут тот же результат, что и выше.

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

Ответ 20

Существует метод Array.sort, который может быть вам полезен. Например:

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