JSON Sort Array Objects JavaScript: Array.sort(), Multi-Key & jq
Last updated:
JavaScript's Array.sort() mutates the original array and sorts by UTF-16 code units by default — always pass a comparator function when sorting JSON objects: array.sort((a, b) => a.age - b.age) for ascending numeric sort, or array.sort((a, b) => b.age - a.age) for descending. String comparison with localeCompare() handles accented characters, case insensitivity, and language-specific collation: array.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })). Multi-key sorting requires a comparator chain: return the first non-zero comparison value, falling back to the next key. This guide covers numeric and string comparators, descending sort, multi-key sorting with a comparator chain, locale-aware sorting with Intl.Collator, stable sort guarantees (V8 guarantees stability since Node.js 11), sorting by nested properties, and jq sort_by() for command-line JSON sorting.
Array.sort() Basics: Numeric and String Comparators
Array.sort() without a comparator converts every element to a string and compares UTF-16 code units — [10, 9, 2].sort() returns [10, 2, 9] because the string "10" sorts before "2". Always pass a comparator function: (a, b) => a - b for ascending numbers, (a, b) => b - a for descending, and (a, b) => a.localeCompare(b) for strings. The comparator return value contract is: negative means a sorts before b, zero means equal (order unspecified in older engines, stable in modern ones), and positive means b sorts before a.
const products = [
{ id: 1, name: 'Widget', price: 19.99, stock: 100 },
{ id: 2, name: 'Gadget', price: 4.99, stock: 250 },
{ id: 3, name: 'Doohickey', price: 49.99, stock: 15 },
{ id: 4, name: 'Thingamajig', price: 9.99, stock: 75 },
];
// ── Ascending numeric sort by price ──────────────────────────────
const byPriceAsc = [...products].sort((a, b) => a.price - b.price);
// [{ price: 4.99 }, { price: 9.99 }, { price: 19.99 }, { price: 49.99 }]
// ── Descending numeric sort by stock ─────────────────────────────
const byStockDesc = [...products].sort((a, b) => b.stock - a.stock);
// [{ stock: 250 }, { stock: 100 }, { stock: 75 }, { stock: 15 }]
// ── Alphabetical string sort by name ─────────────────────────────
const byNameAsc = [...products].sort((a, b) => a.name.localeCompare(b.name));
// [Doohickey, Gadget, Thingamajig, Widget]
// ── Default (broken) sort — never use for numbers ─────────────────
[10, 9, 2, 100, 21].sort();
// [10, 100, 2, 21, 9] ← wrong: string comparison, not numeric
// ── Correct numeric sort ──────────────────────────────────────────
[10, 9, 2, 100, 21].sort((a, b) => a - b);
// [2, 9, 10, 21, 100] ← correct
// ── Sort preserves original — use spread to avoid mutation ────────
const original = [{ id: 3 }, { id: 1 }, { id: 2 }];
const sorted = [...original].sort((a, b) => a.id - b.id);
console.log(original[0].id); // still 3 — original unchanged
console.log(sorted[0].id); // 1 — sorted copyUse the spread operator ([...array]) or array.slice() before calling .sort() to avoid mutating the source array — React state and shared data structures should never be mutated in place. The subtraction trick (a.price - b.price) works correctly for finite numbers but produces incorrect results for NaN, Infinity, and very large integers near Number.MAX_SAFE_INTEGER. For those edge cases, use an explicit comparison: a.value < b.value ? -1 : a.value > b.value ? 1 : 0.
Descending Sort and Reverse
Two patterns produce a descending sort: swap the operands in the comparator (b.price - a.price), or sort ascending and call .reverse(). Swapping operands is more readable for single-field sorts. The reverse-after-sort pattern is useful when you want to build a reusable ascending comparator and invert it separately — but note that .reverse() also mutates in place, so spread the array first if mutation is a concern.
const items = [
{ name: 'Alpha', score: 82 },
{ name: 'Beta', score: 95 },
{ name: 'Gamma', score: 74 },
{ name: 'Delta', score: 95 },
{ name: 'Epsilon', score: 88 },
];
// ── Descending numeric: swap operands ─────────────────────────────
const highestFirst = [...items].sort((a, b) => b.score - a.score);
// [Beta 95, Delta 95, Epsilon 88, Alpha 82, Gamma 74]
// ── Descending string: swap arguments to localeCompare ────────────
const zToA = [...items].sort((a, b) => b.name.localeCompare(a.name));
// [Gamma, Epsilon, Delta, Beta, Alpha]
// ── Reverse pattern: sort ascending then reverse ───────────────────
const ascComparator = (a, b) => a.score - b.score;
const lowestFirst = [...items].sort(ascComparator);
const highestFirst2 = [...lowestFirst].reverse();
// lowestFirst: [Gamma 74, Alpha 82, Epsilon 88, Beta 95, Delta 95]
// highestFirst2: [Delta 95, Beta 95, Epsilon 88, Alpha 82, Gamma 74]
// ── Invert any comparator with a wrapper ──────────────────────────
const invert = (comparator) => (a, b) => -comparator(a, b);
const descScore = invert((a, b) => a.score - b.score);
const result = [...items].sort(descScore);
// [Beta 95, Delta 95, Epsilon 88, Alpha 82, Gamma 74]
// ── Toggle sort direction based on a flag ─────────────────────────
function sortByScore(arr, ascending = true) {
return [...arr].sort((a, b) =>
ascending ? a.score - b.score : b.score - a.score
);
}
const asc = sortByScore(items, true);
const desc = sortByScore(items, false);When stable sort is guaranteed (Node.js 11+, modern browsers), a descending sort of [Beta 95, Delta 95] will preserve Beta before Delta if they were in that order in the original array — because both have score 95, they compare as equal and their original order is preserved. This makes descending sort predictable for UI tables where users expect consistent row order when values are tied.
Multi-Key Sorting: Comparator Chains
Multi-key sorting uses a comparator chain: evaluate the primary comparison, and if it returns zero (equal), evaluate the secondary comparison, and so on. The pattern is: return primaryComparison || secondaryComparison || tertiaryComparison. JavaScript short-circuits || — it returns the first non-zero (truthy) value, falling through to the next comparison only when the current one evaluates to zero. This produces the same semantics as SQL ORDER BY dept ASC, salary DESC.
const employees = [
{ name: 'Alice', department: 'Engineering', salary: 95000 },
{ name: 'Bob', department: 'Marketing', salary: 72000 },
{ name: 'Carol', department: 'Engineering', salary: 110000 },
{ name: 'Dave', department: 'Marketing', salary: 85000 },
{ name: 'Eve', department: 'Engineering', salary: 95000 },
{ name: 'Frank', department: 'HR', salary: 68000 },
];
// ── Sort by department ASC, then salary DESC ──────────────────────
const sorted = [...employees].sort((a, b) => {
const byDept = a.department.localeCompare(b.department);
if (byDept !== 0) return byDept; // primary: department A→Z
return b.salary - a.salary; // secondary: salary high→low
});
// Engineering 110000 (Carol), Engineering 95000 (Alice), Engineering 95000 (Eve),
// HR 68000 (Frank), Marketing 85000 (Dave), Marketing 72000 (Bob)
// ── Three-key sort: dept ASC, salary DESC, name ASC ───────────────
const sorted3 = [...employees].sort((a, b) =>
a.department.localeCompare(b.department) || // 1. department A→Z
(b.salary - a.salary) || // 2. salary high→low
a.name.localeCompare(b.name) // 3. name A→Z (tiebreak)
);
// Engineering: Carol(110k), Alice(95k), Eve(95k) — Alice before Eve alphabetically
// HR: Frank(68k)
// Marketing: Dave(85k), Bob(72k)
// ── Reusable composable comparators ──────────────────────────────
const byDept = (a, b) => a.department.localeCompare(b.department);
const bySalary = (a, b) => a.salary - b.salary;
const byName = (a, b) => a.name.localeCompare(b.name);
const invert = (fn) => (a, b) => -fn(a, b);
const chain = (...fns) => (a, b) => fns.reduce((r, fn) => r !== 0 ? r : fn(a, b), 0);
const comparator = chain(byDept, invert(bySalary), byName);
const result = [...employees].sort(comparator);The || short-circuit pattern is idiomatic JavaScript for comparator chains but has one subtle trap: it only short-circuits on falsy values, and 0 is falsy. This is intentional — zero means equal, so we correctly fall through to the next comparator. The chain() helper using reduce is equivalent and more explicit. For TypeScript codebases, type the comparator as (a: T, b: T) => number and the chain helper as a generic for full type safety.
Locale-Aware Sorting with localeCompare and Intl.Collator
String.prototype.localeCompare() sorts strings according to language rules — handling accented characters, case folding, and locale-specific ordering that the default UTF-16 comparison gets wrong. Intl.Collator is the high-performance alternative: create one instance outside the sort call and reuse its .compare method, avoiding repeated locale data initialization on every comparison.
const people = [
{ name: 'Ångström' },
{ name: 'alice' },
{ name: 'Café' },
{ name: 'ALICE' },
{ name: 'café' },
{ name: 'zebra' },
];
// ── Default sort — wrong: uppercase before lowercase, accents broken
[...people].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
// [ALICE, Café, Ångström, alice, café, zebra] ← uppercase A before lowercase a
// ── localeCompare — case-insensitive, accent-insensitive ──────────
[...people].sort((a, b) =>
a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })
);
// [alice/ALICE/alice, Ångström, Café/café, zebra]
// sensitivity: 'base' ignores case AND accent differences
// ── sensitivity options ───────────────────────────────────────────
// 'base' — ignore case + accents: a === A === á
// 'accent' — ignore case, keep accents: a === A, a !== á
// 'case' — keep case, ignore accents: a !== A, a === á
// 'variant' — keep case + accents: a !== A, a !== á (default)
// ── Intl.Collator — efficient for large arrays ────────────────────
// Create collator ONCE outside the sort call
const collator = new Intl.Collator('en', {
sensitivity: 'base',
numeric: true, // "file10" sorts after "file9", not before
ignorePunctuation: false,
});
const byName = [...people].sort((a, b) => collator.compare(a.name, b.name));
// ── Numeric collation: "file10" after "file9" ─────────────────────
const files = [
{ name: 'file10.json' },
{ name: 'file9.json' },
{ name: 'file2.json' },
{ name: 'file100.json' },
];
const numericCollator = new Intl.Collator('en', { numeric: true });
const sortedFiles = [...files].sort((a, b) =>
numericCollator.compare(a.name, b.name)
);
// [file2.json, file9.json, file10.json, file100.json] ← natural order
// ── German locale — ä,ö,ü sort after a,o,u (not after z) ─────────
const deCollator = new Intl.Collator('de');
['Zürich', 'Bonn', 'Äachen', 'Frankfurt'].sort((a, b) =>
deCollator.compare(a, b)
);
// [Äachen, Bonn, Frankfurt, Zürich] ← correct German alphabetical order
// ── Swedish locale — å,ä,ö are LAST in the alphabet ──────────────
const svCollator = new Intl.Collator('sv');
['Åbo', 'Borås', 'Örebro', 'Malmö'].sort((a, b) =>
svCollator.compare(a, b)
);
// [Borås, Malmö, Örebro, Åbo] ← correct Swedish order (ö,å come after z)The numeric: true collation option enables natural sort order for filenames, version numbers, and any strings containing embedded integers — without it, "file10" sorts before "file2" because "1" < "2" in UTF-16. For multilingual applications, always use the user's locale from navigator.language (browser) or the Accept-Language header (server) rather than hardcoding "en". Intl.Collator is 5–20x faster than repeated localeCompare() calls on large arrays because locale data is parsed only once.
Sorting by Nested Properties
Sorting by a deeply nested property requires accessing it inside the comparator. For optional properties, use optional chaining (?.) and provide a fallback with the nullish coalescing operator (??) so that missing values sort predictably — typically to the beginning (with "" fallback) or the end (with Infinity fallback for numbers). For dynamic property paths at runtime, write a small accessor utility.
const orders = [
{ id: 1, customer: { name: 'Alice', address: { city: 'New York' } }, total: { amount: 150, currency: 'USD' } },
{ id: 2, customer: { name: 'Bob', address: { city: 'Chicago' } }, total: { amount: 89, currency: 'USD' } },
{ id: 3, customer: { name: 'Carol', address: null }, total: { amount: 220, currency: 'USD' } },
{ id: 4, customer: { name: 'Dave', address: { city: 'Atlanta' } }, total: { amount: 150, currency: 'USD' } },
];
// ── Sort by nested city — with null safety ────────────────────────
const byCity = [...orders].sort((a, b) => {
const cityA = a.customer?.address?.city ?? ''; // null/undefined → ''
const cityB = b.customer?.address?.city ?? '';
return cityA.localeCompare(cityB, 'en', { sensitivity: 'base' });
});
// ['' (Carol, missing city), Atlanta (Dave), Chicago (Bob), New York (Alice)]
// ── Sort by nested numeric value ──────────────────────────────────
const byAmount = [...orders].sort((a, b) =>
(a.total?.amount ?? 0) - (b.total?.amount ?? 0)
);
// [89, 150, 150, 220]
// ── Nulls last: push missing values to the end ────────────────────
const byAmountNullsLast = [...orders].sort((a, b) => {
const amtA = a.total?.amount ?? Infinity; // missing → sort last
const amtB = b.total?.amount ?? Infinity;
return amtA - amtB;
});
// ── Dynamic path accessor ─────────────────────────────────────────
function get(obj, path) {
return path.split('.').reduce((o, key) => o?.[key], obj);
}
function sortByPath(arr, path, direction = 'asc') {
return [...arr].sort((a, b) => {
const va = get(a, path) ?? '';
const vb = get(b, path) ?? '';
const cmp = typeof va === 'number'
? va - vb
: String(va).localeCompare(String(vb), 'en', { sensitivity: 'base' });
return direction === 'asc' ? cmp : -cmp;
});
}
sortByPath(orders, 'customer.address.city', 'asc');
sortByPath(orders, 'total.amount', 'desc');The get() path accessor is useful for table components that sort by column when the column key is a dot-notation string like "customer.address.city". Choose your null fallback based on the desired UX: '' or 0 places nulls first in ascending sort; Infinity or 'zzz' places nulls last. Be consistent — mixing fallback strategies across columns in a table produces a confusing user experience where null rows jump position when you change sort columns.
Stable Sort: Guarantees and Browser Support
A stable sort guarantees that elements that compare as equal retain their original relative order in the output. ECMAScript 2019 (ES2019) mandated stable Array.prototype.sort() across all engines. V8 (Chrome 70, Node.js 11, released 2018) was the last major engine to adopt TimSort — the same stable algorithm used by Python and Java. Stability is critical for multi-pass sorting and for maintaining secondary sort orders applied earlier.
// ── Why stability matters: multi-pass sorting ─────────────────────
// Goal: sort by department ASC, then within each department by hire date ASC
// Approach: sort by hire date first, then sort by department
// A stable department sort will preserve the hire-date order within each group.
const staff = [
{ name: 'Alice', dept: 'Eng', hired: '2020-03-01' },
{ name: 'Bob', dept: 'HR', hired: '2019-07-15' },
{ name: 'Carol', dept: 'Eng', hired: '2018-11-20' },
{ name: 'Dave', dept: 'HR', hired: '2022-01-10' },
{ name: 'Eve', dept: 'Eng', hired: '2021-06-05' },
];
// Pass 1: sort by hire date ascending
const byHireDate = [...staff].sort((a, b) =>
a.hired.localeCompare(b.hired) // ISO dates sort lexicographically
);
// [Carol 2018, Bob 2019, Alice 2020, Eve 2021, Dave 2022]
// Pass 2: stable sort by department — hire date order preserved within groups
const byDeptThenDate = [...byHireDate].sort((a, b) =>
a.dept.localeCompare(b.dept)
);
// Eng: [Carol 2018, Alice 2020, Eve 2021] ← hire order preserved
// HR: [Bob 2019, Dave 2022] ← hire order preserved
// ── Verify stability — elements with equal keys keep input order ───
const items = [
{ key: 'A', order: 1 },
{ key: 'B', order: 2 },
{ key: 'A', order: 3 }, // same key as first, but input order = 3
{ key: 'B', order: 4 },
];
const stable = [...items].sort((a, b) => a.key.localeCompare(b.key));
// Stable result: [{key:'A',order:1},{key:'A',order:3},{key:'B',order:2},{key:'B',order:4}]
// order:1 before order:3 within A — original relative order preserved
// order:2 before order:4 within B — original relative order preserved
// ── Node.js version matrix ────────────────────────────────────────
// Node.js 10 (V8 6.6): unstable (insertion sort below 10 elements, quicksort above)
// Node.js 11 (V8 7.0): STABLE TimSort — all array sizes
// Node.js 12+: STABLE — guaranteed by spec (ES2019)
// Chrome 70+: STABLE
// Firefox 3+: STABLE (always was)
// Safari 10.1+: STABLEIf you must support Node.js 10 or older browsers, implement a stable sort fallback: decorate each element with its original index before sorting (arr.map((v, i) => [v, i])), use the index as a final tiebreaker in the comparator (a[1] - b[1]), then strip the decoration. This technique — known as a Schwartzian transform — guarantees stability regardless of the engine's native sort algorithm. In 2026, Node.js 10 is long past end-of-life and no production environment should still use it.
jq sort_by(): Command-Line JSON Sorting
jq is a command-line JSON processor that sorts arrays with sort_by(expr). It produces an ascending sort by default; pipe through reverse for descending. sort_by() accepts any jq expression — nested fields, string transformations, or computed values. jq's sort is stable and handles mixed types by sorting them in the type order: null < false < true < numbers < strings < arrays < objects.
# Input: products.json
# [{"name":"Widget","price":19.99},{"name":"Gadget","price":4.99},{"name":"Doohickey","price":49.99}]
# ── Sort by price ascending ───────────────────────────────────────
jq 'sort_by(.price)' products.json
# [{"name":"Gadget","price":4.99},{"name":"Widget","price":19.99},{"name":"Doohickey","price":49.99}]
# ── Sort by price descending ──────────────────────────────────────
jq 'sort_by(.price) | reverse' products.json
# ── Sort by string field ──────────────────────────────────────────
jq 'sort_by(.name)' products.json
# ── Case-insensitive string sort ──────────────────────────────────
jq 'sort_by(.name | ascii_downcase)' products.json
# ── Multi-key sort: department ASC, salary DESC ───────────────────
# jq sort_by accepts a comma-separated expression list for multi-key sort
jq 'sort_by(.department, .salary)' employees.json
# ── Multi-key: dept ASC, salary DESC (negate numeric for descending)
jq 'sort_by(.department, -.salary)' employees.json
# ── Sort by nested property ───────────────────────────────────────
jq 'sort_by(.address.city)' orders.json
# ── Sort by array length ──────────────────────────────────────────
jq 'sort_by(.tags | length)' posts.json
# ── Sort and select top N ─────────────────────────────────────────
jq 'sort_by(.score) | reverse | .[0:3]' scores.json # top 3 highest scores
# ── Sort keys within each object (not the array) ─────────────────
# to_entries | sort_by(.key) | from_entries sorts object keys alphabetically
jq '[.[] | to_entries | sort_by(.key) | from_entries]' data.json
# ── Sort from stdin (pipe) ────────────────────────────────────────
curl -s https://api.example.com/products | jq 'sort_by(.price)'
# ── Sort and write back to file ───────────────────────────────────
jq 'sort_by(.name)' data.json > sorted.json
# Or in place with sponge (moreutils):
jq 'sort_by(.name)' data.json | sponge data.jsonThe negation trick for descending numeric sort (-.salary) works because jq evaluates the expression and sorts by the resulting value — a negated number sorts descending. This does not work for strings (negation is not defined for strings in jq); for descending string sort, use sort_by(.name) | reverse. For more on jq's full filter language, see the jq JSON command line guide. To sort JSON arrays as part of a broader JSON transform pipeline, jq pipelines can chain sort_by, map, select, and group_by in a single expression.
Key Terms
- comparator function
- A function passed to
Array.sort()that defines the sort order by accepting two elements (aandb) and returning a number. A negative return value placesabeforeb; zero leaves their order unchanged (stable sort); a positive return value placesbbeforea. The simplest numeric ascending comparator is(a, b) => a - b. Without a comparator,Array.sort()coerces elements to strings and compares UTF-16 code units — producing incorrect results for numbers and non-ASCII strings. The comparator must be consistent: iff(a, b) < 0thenf(b, a) > 0must also hold; violating this contract produces undefined sort behavior. - localeCompare()
- A
String.prototypemethod that compares two strings according to the sort order of a specified locale, returning a negative, zero, or positive number suitable for use as a comparator return value. Syntax:str.localeCompare(compareStr, locale, options). Thelocaleparameter is a BCP 47 language tag ("en","de","sv"). Theoptionsparameter controlssensitivity(which differences are considered),numeric(natural number sorting), andignorePunctuation.localeCompare()correctly sorts accented characters, handles case folding, and respects language-specific alphabetical order — unlike the<and>operators which compare raw UTF-16 code units. For performance-critical sorting, useIntl.Collatorinstead. - Intl.Collator
- A built-in JavaScript object (
Intl.Collator) that provides locale-sensitive string comparison. UnlikelocaleCompare(), which parses locale data on every call,Intl.Collatorloads and caches locale data once at construction time, making repeated comparisons 5–20x faster for large arrays. Usage:const collator = new Intl.Collator(locale, options); then usecollator.compare(a, b)as the comparator function. Accepts the same options aslocaleCompare():sensitivity,numeric,caseFirst, andcollation.Intl.Collatoris part of the ECMAScript Internationalization API (ECMA-402) and is supported in all modern browsers and Node.js 0.12+. Always prefer it overlocaleCompare()when sorting arrays with more than a few dozen elements. - stable sort
- A sort algorithm is stable if it preserves the original relative order of elements that compare as equal. Given two elements
aandbwhere the comparator returns0, a stable sort guarantees thataappears beforebin the output ifaappeared beforebin the input. ECMAScript 2019 (ES2019) mandated stableArray.prototype.sort(). V8 (Chrome 70, Node.js 11) adopted TimSort in 2018, becoming the last major engine to guarantee stability. Stability matters for multi-pass sorting — sorting by a secondary key and then by a primary key (while preserving secondary order within equal primary groups) only works correctly with a stable sort. - multi-key sort
- A sort that orders elements by more than one property, applying the next key as a tiebreaker when two elements are equal on the current key. In JavaScript, implemented with a comparator chain:
(a, b) => primaryComparison(a, b) || secondaryComparison(a, b). The||operator returns the first non-zero (truthy) value — zero (equal) is falsy, so the chain falls through to the next comparator. Multi-key sort is equivalent to SQLORDER BY col1 ASC, col2 DESC. Each key in the chain can have its own direction: ascending or descending. The comparators can be composed with helper functions to build reusable, readable sort specifications. - jq sort_by()
- A jq built-in filter that sorts a JSON array by the value produced by a jq expression evaluated on each element. Syntax:
sort_by(expr)orsort_by(expr1, expr2)for multi-key sort. Always sorts ascending; pipe throughreversefor descending. For descending numeric sort, negate the expression:sort_by(-.price). For case-insensitive string sort, normalize withascii_downcase:sort_by(.name | ascii_downcase). jq's sort is stable and defines a type ordering for mixed-type arrays:null < false < true < numbers < strings < arrays < objects.sort_by(.key)is equivalent to JavaScript'sarray.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).
FAQ
How do I sort a JSON array in JavaScript?
Use Array.prototype.sort() with a comparator function. For numeric ascending: array.sort((a, b) => a.age - b.age). For alphabetical strings: array.sort((a, b) => a.name.localeCompare(b.name)). Never use the default sort (no comparator) — it converts elements to strings and compares UTF-16 code units, making [10, 9, 2].sort() return [10, 2, 9]. Always pass an explicit comparator when sorting JSON objects or numeric values. Because Array.sort() mutates the original array, use a spread copy first: [...array].sort((a, b) => a.price - b.price) to preserve the original.
How do I sort JSON objects by a property?
Access the property inside the comparator: array.sort((a, b) => a.price - b.price) for ascending numeric sort by price, or array.sort((a, b) => a.name.localeCompare(b.name)) for ascending alphabetical sort by name. The comparator receives two objects (a and b) and must return a negative number (a first), zero (equal), or positive number (b first). For string properties, always use localeCompare() — the < and > operators compare raw UTF-16 code units and incorrectly handle accented characters, uppercase/lowercase, and language-specific collation.
How do I sort a JSON array in descending order?
Swap the operands in the comparator: array.sort((a, b) => b.price - a.price) for descending numeric sort, or array.sort((a, b) => b.name.localeCompare(a.name)) for descending alphabetical sort. Alternatively, sort ascending and then call .reverse(): [...array].sort((a, b) => a.price - b.price).reverse(). Both approaches produce the same result. The reverse-operand pattern is more direct; the .reverse() pattern is useful when you have an existing ascending comparator function you want to reuse. Note that .reverse() also mutates in place — use a spread copy if you need to preserve the sorted-ascending version.
How do I sort by multiple fields?
Use a comparator chain with the || short-circuit operator: array.sort((a, b) => a.department.localeCompare(b.department) || a.salary - b.salary). This sorts by department alphabetically, then by salary ascending as a tiebreaker. The || operator returns the first non-zero value — zero (equal) is falsy, so the chain falls through to the next comparison. You can extend the chain to as many fields as needed: primaryComparison || secondaryComparison || tertiaryComparison. Each comparison can be ascending or descending independently. For a more explicit approach, use an if block: if (byDept !== 0) return byDept; return a.salary - b.salary;.
How do I sort strings alphabetically in JSON?
Use localeCompare() with the sensitivity: 'base' option for case-insensitive, accent-insensitive alphabetical sort: array.sort((a, b) => a.name.localeCompare(b.name, "en", { sensitivity: "base" })). This correctly handles accented characters (é, ñ, ü), treats uppercase and lowercase as equal, and follows language-specific alphabetical order. For large arrays, use Intl.Collator instead — create one instance outside the sort call and reuse its .compare method: const collator = new Intl.Collator('en', { sensitivity: "base" }); array.sort((a, b) => collator.compare(a.name, b.name)). This is 5–20x faster than repeated localeCompare() calls on large datasets.
What is stable sort in JavaScript?
Stable sort preserves the original relative order of elements that compare as equal. If a and b have the same sort key value (comparator returns 0), a stable sort guarantees a appears before b in the output if a appeared before b in the input. ECMAScript 2019 mandated this behavior for Array.prototype.sort(). V8 (Node.js 11+, Chrome 70+) implemented TimSort in 2018 to meet this guarantee. Stable sort is essential for multi-pass sorting: sort by salary first, then stably sort by department — employees with the same department remain in salary order within each department group.
How do I sort by a nested property?
Access the nested property chain inside the comparator: array.sort((a, b) => a.address.city.localeCompare(b.address.city)) for a string nested property, or array.sort((a, b) => a.stats.score - b.stats.score) for a number. For optional nested properties, add null safety with optional chaining and a fallback: array.sort((a, b) => (a.address?.city ?? "").localeCompare(b.address?.city ?? "")). The ?? fallback '' sorts missing values first in ascending alphabetical order; use 'zzz' or Infinity to sort missing values last. For runtime dot-notation paths, write a path accessor: const get = (obj, path) => path.split(".").reduce((o, k) => o?.[k], obj).
How do I sort a JSON array with jq?
Use sort_by(.fieldName) for ascending sort: jq 'sort_by(.price)' data.json. For descending: jq 'sort_by(.price) | reverse' data.json, or negate numeric fields: jq 'sort_by(-.price)' data.json. For multi-key sort: jq 'sort_by(.department, .salary)' data.json. For nested properties: jq 'sort_by(.address.city)' data.json. For case-insensitive string sort: jq 'sort_by(.name | ascii_downcase)' data.json. jq's sort_by() is a stable sort and handles mixed-type arrays by sorting in type order: null before false before true before numbers before strings before arrays before objects. See the jq JSON command line guide for more filter patterns.
Further reading and primary sources
- MDN: Array.prototype.sort() — Official documentation for Array.sort() comparator function, stability, and examples
- MDN: String.prototype.localeCompare() — localeCompare() parameters, sensitivity options, and locale-aware string comparison
- MDN: Intl.Collator — Intl.Collator constructor, options, and the compare() method for high-performance locale sorting
- ECMAScript 2019: Stable Array.sort — ES2019 specification mandating stable Array.prototype.sort() across all JavaScript engines
- jq Manual: sort, sort_by — jq sort_by() filter reference with multi-key sort, type ordering, and expression examples
For searching and filtering sorted arrays, see the JSON search and filter guide. For broader data transformations, see the JSON transform guide. For JSON performance considerations when sorting large datasets, see the performance guide.