Сортировка массива объектов по строке и числовым значениям, ведущим себя по-разному

Этот вопрос немного многословен, но, пожалуйста, потерпите меня.

У меня есть универсальная функция сравнения значений для использования с .sort():

export function compareObjectValues(key: string, direction: string = 'asc') {
  // used as a comparator supplied to .sort() for sorting arrays of objects

  return function(a: Object, b: Object): number {
    const propertyA = getDescendantProperty(a, key);
    const propertyB = getDescendantProperty(b, key);

    if (!propertyA || !propertyB) {
      return 0;
    }


    const normalizedPropA = (typeof propertyA === 'string') ? (propertyA as string).toLocaleLowerCase() : propertyA;
    const normalizedPropB = (typeof propertyB === 'string') ? (propertyB as string).toLocaleLowerCase() : propertyB;
    let comparison = 0;

    if (normalizedPropA > normalizedPropB) {
      comparison = 1;
    }
    else {
      comparison = -1;
    }

    if (typeof normalizedPropA === 'number' && typeof normalizedPropB === 'number') {
      console.info('comparison', comparison);
      return comparison;
    }
    else {
      console.info('string comparison', comparison);
      return (direction === 'desc') ? (comparison * -1) : comparison;
    }
  };
}

Внутренняя вспомогательная функция getDescendantProperty() предназначена для безопасного доступа к значениям объекта по точечному пути:

export function getDescendantProperty(obj: object, path: string) {
  // safely access nested properties if the property of the object is itself, an object

  if (path.includes('.')) {
    return path.split('.').reduce((accumulator: object, part: string) => accumulator && accumulator[part] || undefined, obj);
  }
  else {
    return obj[path];
  }
}

Когда я сортирую массив объектов, используя строку для сортировки, compareObjectValues() работает как положено. При использовании числовых значений происходит сбой и не перебираются все ожидаемые значения для сравнения.

Пример:

describe('on an object that uses a number for sorting', () => {
  it('should return the comparison of the values in the correct order', () => {
    const objects = [
      { id: 4 },
      { id: 0 },
      { id: 2 },
      { id: 3 },
      { id: 1 },
    ];

    const sortedObjects = objects.sort(utils.compareObjectValues('id'));

    expect(sortedObjects).toEqual([{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]);
  });
});

Выход кармы:

LOG: 'comparison', 1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 0 of 2 SUCCESS (0 secs / 0 secs)
LOG: 'comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 0 of 2 SUCCESS (0 secs / 0 secs)
LOG: 'comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 0 of 2 SUCCESS (0 secs / 0 secs)
Chrome 74.0.3729 (Mac OS X 10.14.5) Utils tests When calling `compareObjectValues` on an object that uses a number for sorting should return the comparison of the numbers in the correct order FAILED
        Error: Expected $[0].id = 4 to equal 0.
        Expected $[1].id = 0 to equal 1.
        Expected $[2].id = 1 to equal 2.
        Expected $[3].id = 2 to equal 3.
        Expected $[4].id = 3 to equal 4.
            at <Jasmine>
            at UserContext.<anonymous> (src/app/lib/utils.spec.ts:133:31)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:1)
            at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:308:1)
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
Chrome 74.0.3729 (Mac OS X 10.14.5) Utils tests When calling `compareObjectValues` on an object that uses a number for sorting should return the comparison of the numbers in the correct order FAILED
        Error: Expected $[0].id = 4 to equal 0.
        Expected $[1].id = 0 to equal 1.
        Expected $[2].id = 1 to equal 2.
        Expected $[3].id = 2 to equal 3.
        Expected $[4].id = 3 to equal 4.
            at <Jasmine>
            at UserContext.<anonymous> (src/app/lib/utils.spec.ts:133:31)
            at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:1)
LOG: 'string comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', 1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', 1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', 1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', -1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
LOG: 'string comparison', 1
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 1 of 2 (1 FAILED) (0 secs / 0.159 secs)
Chrome 74.0.3729 (Mac OS X 10.14.5): Executed 2 of 2 (1 FAILED) (0.19 secs / 0.16 secs)
TOTAL: 1 FAILED, 1 SUCCESS
TOTAL: 1 FAILED, 1 SUCCESS
npm ERR! Test failed.  See above for more details.

Как видно из лога, при обработке объектов с числовыми значениями он остановился после 3-х итераций. Со строковыми значениями он обработал 9.

Я проверил, что getDescendantProperty() возвращает ожидаемое значение. Я также проверил, что логика compareObjectValues() работает должным образом, если я применяю те же сравнения вручную, передавая функцию .sort() в своем тесте.

У меня возникли трудности с идентификацией моей ошибки, и я был бы очень признателен за помощь.

if (!propertyA || !propertyB) { return 0; } ? Также известно как "отказаться от 0 по какой-то причине
jcalz 30.05.2019 21:03
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
1
35
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема заключается в этой ленивой проверке свойств:

if (!propertyA || !propertyB) return 0;

Это прекрасно работает для проверки определенных типов значений, но даст неожиданные результаты при проверке чисел (!0 === true), пустых строк (!'' === true) и т. д.

Вместо этого используйте строгие проверки:

if (typeof propertyA === 'undefined' || typeof propertyB === 'undefined') return 0;

Позвольте мне попробовать

Brandon Taylor 30.05.2019 21:05

Я должен был уловить это. Большое спасибо!

Brandon Taylor 30.05.2019 21:07

Другие вопросы по теме