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

103

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

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

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

Краткий пример:

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

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

Innovine
источник
Приведите пример объекта, который вы пытаетесь преобразовать в строку (вывод JSON или литерал объекта JS)
Bojangles
4
Не гарантируется, что ключи объектов в объектах имеют фиксированный порядок. Это сделано намеренно.
Прохожий
1
stackoverflow.com/questions/1359761/sorting-a-javascript-object может вам помочь
Satpal
1
@rab, где ты это узнал? Примечание: это может сработать (и, вероятно, работает большую часть времени), но я имел в виду, что это не гарантируется.
Dogbert
1
ОП здесь. Здесь не было зависимости от порядка свойств, просто вопрос о том, как избежать различий в сериализованных данных. В конечном итоге это было решено путем сохранения свойств в массивах и их сортировки перед сериализацией, как в принятом ответе ниже.
Innovine

Ответы:

80

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

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}
Marksyzm
источник
9
Это как раз то, чего я пытался избежать :) Но спасибо, кажется правильным, хотя и тяжелым, решением.
Innovine
1
В этом коде есть ошибка. Вы не можете ссылаться obj[i]в цикле for, потому что iэто целое число, а имена свойств не обязательно (отсюда и этот вопрос). Так и должно быть obj[arr[i]].
Эндрю Энсли
1
Обратные вызовы не являются исключительными для асинхронного поведения.
marksyzm
2
@Johann Используя здесь обратный вызов, он превращается iterateObjectAlphabeticallyв функцию многократного использования. Без обратного вызова «код полезной нагрузки» должен был бы находиться внутри самой функции. Я думаю, что это очень элегантное решение, которое используется во многих библиотеках и самом ядре JS. Например, Array.forEach .
Stijn de Witt
1
@marksyzm Ничего страшного, в моем случае нужно было строковить только набор полей. Таким образом, ваш ответ сбил меня с толку. Спасибо
Benj
39

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

С сайта проекта:

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

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

Стейн де Витт
источник
1
Обратите внимание, что это репо в плохой форме: не обновлялось в течение 3 лет и имеет нерешенные проблемы и запросы на вытягивание. Я думаю, что лучше поискать новые ответы.
Том
3
@Tom Также обратите внимание, что это репо имеет 5 миллионов загрузок еженедельно (!). Другими словами, его активно используют. Наличие «нерешенных» открытых проблем и / или запросов на вытягивание не имеет большого значения. Просто автор (ы) не заботится об этих проблемах или у них нет времени и т. Д. Не каждую библиотеку нужно обновлять каждые пару месяцев. На самом деле, imho, лучшие библиотеки обновляются очень редко. Когда что-то сделано, дело сделано. Я не говорю, что у этого проекта нет реальных проблем, но если да, укажите на них. Я не участвую в этой библиотеке BTW.
Stijn de Witt
ES6 Я думаю, что сломал его возможность сортировать объекты (не массивы), как указано. Мне это нужно для редактирования пользователем. Даже если для компьютера порядок не имеет значения. Это происходит с пользователями, редактирующими его. Стоило попробовать.
TamusJRoyce
1
Помимо того, что говорит @Tom, я также отмечу, что этот проект не имеет зависимостей времени выполнения. Это означает, что если в этом репо нет проблем с безопасностью / обслуживанием, то, вероятно, он довольно стабилен и безопасен в использовании. Кроме того, я только что протестировал его с ES6, и, похоже, он работает нормально (по крайней мере, в Firefox).
Фил
28

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

JSON.stringify(obj, Object.keys(obj).sort())
Кристиан д'Эрёз
источник
1
Есть ли какие-либо документально подтвержденные гарантии такого поведения?
Rich Remer
4
@richremer Да, в стандарте ECMAScript второй параметр JSON.stringify()вызывается replacerи может быть массивом. Он используется для создания, PropertyListкоторый затем используется SerializeJSONObject()для добавления свойств в указанном порядке. Это задокументировано, начиная с ECMAScript 5.
Christian d'Heureuse
15
Обратите внимание: если в obj есть вложенные объекты, вложенные ключи также должны присутствовать во втором аргументе; в противном случае они будут отброшены! Контрпример: > JSON.stringify({a: {c: 1, b: 2}}, ['a']) '{"a":{}}' один (хакерский) способ создать список всех соответствующих ключей - использовать JSON.stringify для обхода объекта: function orderedStringify(obj) { const allKeys = []; JSON.stringify(obj, (k, v) => { allKeys.push(k); return v; }); return JSON.stringify(obj, allKeys.sort()); } Пример:> orderedStringify({a: {c: 1, b: 2}}) '{"a":{"b":2,"c":1}}'
Саиф Хаким
1
Object.keys () не рекурсивен. Это не сработает для любого объекта, содержащего вложенные массивы или объекты.
Jor
28

Я не понимаю, зачем нужна сложность текущих лучших ответов, чтобы рекурсивно получить все ключи. Если не требуется идеальная производительность, мне кажется, что мы можем просто позвонить 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 );
}
Джор
источник
это интересно, но вы на самом деле «обманули», используя stringify для отображения всех ключей в массив. Этот массив может быть проще, лучше и безопаснее получить с помощью Object.keys
Бернардо Дал Корно
6
Нет, дело в том, что Object.keys()это не рекурсивно, поэтому, если у вас есть такой объект, {a: "A", b: {c: "C"} }и вы вызываете на нем Object.keys, вы получите только ключи aи b, но не c. JSON.stringifyзнает все о рекурсии для каждого типа объекта, обрабатываемого процессом сериализации JSON, будь то объектный или массив (и он уже реализует логику для распознавания обоих), поэтому он должен обрабатывать все правильно для нас.
Jor
1
Мне нравится это решение, оно кажется очень элегантным и надежным.
Маркус
@Jor спасибо, это работает для меня. Другие фрагменты, которые я пробовал, так или иначе не увенчались успехом.
nevf
14

Обновление 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 с https://stackoverflow.com/a/29622653/94148

function orderedJsonStringify(o) {
  return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}));
}
алын
источник
Не совсем правильный синтаксис. Должно быть -function orderedJsonStringify(o) { return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {})); }
Бен
На данный момент это устранило мою проблему с C # DataContractJsonSerializer и «__type», не указанным первым в строке json. Спасибо.
Yogurt The Wise
2
Обратите внимание, что это, как и ответ Кристиана, неправильно работает с вложенными объектами.
Дэниел Гриском
sortObjPropertiesByKey не определен. Вы хотели использовать sortObjByKey?
Пол Линч
Не похоже, что это sortObjByKey()проверяет циклические ссылки, поэтому будьте осторожны, это может зависнуть в бесконечном цикле в зависимости от входных данных. Пример: var objA = {}; var objB = {objA: objA}; objA.objB = objB;-> sortObjByKey(objA);->VM59:6 Uncaught RangeError: Maximum call stack size exceeded
Klesun
4

Это то же самое, что и ответ Сатпала Сингха

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"}"
Гиридхар ЧР
источник
3

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

Глянь сюда:

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

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

Вот небольшой пример jsfiddle:

http://jsfiddle.net/Eq2Yw/

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

Редактировать:

Также см. Этот вопрос SO относительно недетерминированного вывода stringify, особенно подробности Daff о различиях браузеров:

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

Дэйв Р.
источник
Свойства каждого объекта известны (и в основном это строки), поэтому жесткое кодирование свойств в моем собственном строковом преобразователе toJSON может быть намного быстрее, чем сортировка ...!
Innovine
Приведенный ниже «OrderJsonStringify» почти сработал. Но мне кажется, что это лучшее решение. Когда мне нужно, чтобы '__type' C # DataContractJsonSerializer был первым элементом в строке json. var json = JSON.stringify (myjsonobj, ['__type', 'id', 'text', 'somesubobj' и т. д. .....]); немного больно иметь список всех ключей.
Yogurt The Wise
3

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

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);
Джейсон Пархэм
источник
reorderследует переименовать в, sortObjectи это не обрабатывает массивы
Джонатан Гаврих
Спасибо, что заметили это, и проблема OP заключалась в использовании объектов, а не массивов.
Джейсон Пархэм
@JonathanGawrych Зачем вам все равно сортировать массивы, они должны иметь порядок. Массив [1,2,3] не то же самое, что массив [2,3,1], но объект не имеет порядка свойств. Проблема в том, что порядок внезапно имеет значение после преобразования в строку - строки для 100% эквивалентных объектов могут быть разными. К сожалению, попытки получить детерминированную строку кажутся значительными, пример пакета: github.com/substack/json-stable-stringify/blob/master/index.js
Mörre
1
@ Mörre - Думаю, я не был достаточно конкретным. Под «не обрабатывает массивы» я имел в виду, что массивы сломаны, а не остаются несортированными. Проблема в том, что typeof arr === "object"он преобразует массивы в объекты. Например, var obj = {foo: ["bar", "baz"]}было бы преобразовано в{ "foo": { "0": "bar", "1": "baz"} }
Джонатан Гаврих
чтобы исправить проблему, обнаруженную @JonathanGawrych:if(obj.constructor.prototype !== Object.prototype)
ricka,
3

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

function sortObjectByPropertyName(obj) {
    return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}
Майки Найки
источник
Может быть, имя было бы лучше sortObjectPropertiesByName()или проще sortPropertiesByName().
янв.
3

Я взял ответ от @Jason Parham и внес некоторые улучшения

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}]}
Питер
источник
2

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

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

Работает с 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.

AJP
источник
2
Спасибо, но у меня была эта проблема 4 года назад, я рад сообщить, что с тех пор я немного переехал :)
Innovine
1
:) Рад это слышать. Вчера Хотя я имел эту проблему и не нашли ответ , который я искал под (старый , но все еще актуально) вопрос :)
AJP
0

Пытаться:

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);
3 года 3
источник
Спасибо, но, судя по всему, порядок не определен и работает только. Но интересный подход!
Innovine
0

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

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

раб
источник
1
Работает хорошо, но, к сожалению, не рекурсивно.
Джонатан Гаврих 02
0

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

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

Нильс Гьединг Ольсен
источник
0
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," a ": [{" h ": 3," j ": 8}, {" a ": 3," b ": 7}]," b ": {" e " : 9, "y": 4, "z": 2}, "c": 9} "

Saintthor
источник
Пожалуйста, объясните, почему используется внешняя библиотека, и, как сказал @DeKaNszn, добавьте некоторые пояснения и введение. Было бы признательно.
Бендж
эта функция скопирована из моего проекта. в котором я использую underscore.js.
saintthor
0

Расширение ответа 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;
}
gblff
источник
0

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

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

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

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

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

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

import { isEqual } from "lodash-es";

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

export const distinctUntilEqualChanged = <T>(): MonoTypeOperatorFunction<T> => 
                                                pipe(distinctUntilChanged(isEqual));
Simon_Weaver
источник
0

В конце концов, ему нужен массив, который кэширует все ключи во вложенном объекте (в противном случае он будет пропускать некэшированные ключи). Самый старый ответ просто неверен, потому что второй аргумент не заботится о точечной нотации. Итак, ответ (с помощью Set) становится.

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
источник
0

Вот подход клонирования ... клонируйте объект перед преобразованием в 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;
}

Если он очень новый, но, похоже, отлично работает с файлами package.json.

Кори Аликс
источник
-3

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

yourBigArray.sort(function(a,b){
    //custom sorting mechanism
});
Егор4ег
источник
1
Однако я не хочу сортировать массив. На самом деле часть массива не важна ... Я хочу отсортировать свойства объекта ... из нескольких быстрых экспериментов похоже, что свойства перечислены в том порядке, в котором они созданы. Одним из способов может быть создание нового объекта и копирование свойств в алфавитном порядке, но я надеюсь, что есть что-то более простое / быстрое ...
Innovine
1
@Innovine, даже это не сработает, поскольку ключи не гарантируются во время создания по спецификации.
Dogbert
Тогда возникает вопрос, как мне структурировать мои объекты таким образом, чтобы ключи были в фиксированном порядке .. для меня не невозможно сделать for (key in obj) и сохранить их во временном массиве и отсортировать это и вручную построить строку .. но я отчаянно надеюсь, что есть более простой способ
Innovine
Нет, это в значительной степени способ сделать это. Добро пожаловать в JavaScript.
marksyzm