Skip to content

Commit 918d346

Browse files
committed
feat: sort_by function
1 parent 1c1b633 commit 918d346

File tree

3 files changed

+86
-16
lines changed

3 files changed

+86
-16
lines changed

Diff for: packages/jmespath/src/functions/Functions.ts

+49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Expression, getType, isNumber, isRecord } from '../visitor/utils';
22
import type { JSONArray, JSONObject, JSONValue } from '../types';
33
import { typeCheck, arityCheck } from './typeChecking';
4+
import { JMESPathTypeError } from '../errors';
45

56
/**
67
* TODO: validate SignatureDecorator type and extract to a separate file
@@ -261,6 +262,54 @@ class Functions {
261262
return arg.sort();
262263
}
263264

265+
/**
266+
* Sort the provided array by the provided expression.
267+
*
268+
* @param arg The array to sort
269+
* @param expression The expression to sort by
270+
* @returns The sorted array
271+
*/
272+
@Functions.signature({
273+
argumentsSpecs: [['array'], ['expression']],
274+
})
275+
public funcSortBy(
276+
args: Array<JSONValue>,
277+
expression: Expression
278+
): Array<unknown> {
279+
return args
280+
.map((value, index) => {
281+
const visited = expression.visit(value);
282+
const type = getType(visited);
283+
if (type !== 'string' && type !== 'number') {
284+
throw new JMESPathTypeError({
285+
currentValue: visited,
286+
expectedTypes: ['string'],
287+
actualType: getType(visited),
288+
});
289+
}
290+
291+
return {
292+
value,
293+
index,
294+
visited: visited ? visited : null,
295+
};
296+
})
297+
.sort((a, b) => {
298+
if (a.visited === null && b.visited === null) {
299+
return 0;
300+
} else if (a.visited === null) {
301+
return -1;
302+
} else if (b.visited === null) {
303+
return 1;
304+
} else if (a.visited === b.visited) {
305+
return a.index - b.index; // Make the sort stable
306+
} else {
307+
return a.visited > b.visited ? 1 : -1;
308+
}
309+
})
310+
.map(({ value }) => value); // Extract the original values
311+
}
312+
264313
/**
265314
* Determines if the provided string starts with the provided suffix.
266315
*

Diff for: packages/jmespath/src/functions/typeChecking.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getType, isRecord } from '../visitor/utils';
1+
import { Expression, getType, isRecord } from '../visitor/utils';
22
import { JMESPathTypeError, ArityError, VariadicArityError } from '../errors';
33

44
/**
@@ -97,7 +97,19 @@ const typeCheckArgument = (arg: unknown, argumentSpec: Array<string>): void => {
9797
}
9898
break;
9999
} else {
100-
if (type === 'string' || type === 'number' || type === 'boolean') {
100+
if (type === 'expression') {
101+
if (!(arg instanceof Expression)) {
102+
if (!hasMoreTypesToCheck) {
103+
throw new JMESPathTypeError({
104+
currentValue: arg,
105+
expectedTypes: argumentSpec,
106+
actualType: getType(arg),
107+
});
108+
}
109+
continue;
110+
}
111+
break;
112+
} else if (type === 'string' || type === 'number' || type === 'boolean') {
101113
if (typeof arg !== type) {
102114
if (!hasMoreTypesToCheck) {
103115
throw new JMESPathTypeError({

Diff for: packages/jmespath/tests/unit/functions.test.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -1606,7 +1606,7 @@ describe('Functions tests', () => {
16061606
}
16071607
);
16081608

1609-
/* it.each([
1609+
it.each([
16101610
{
16111611
description: 'sort by field expression',
16121612
expression: 'sort_by(people, &age)',
@@ -1734,6 +1734,16 @@ describe('Functions tests', () => {
17341734
expression: 'sort_by(`[]`, &age)',
17351735
expected: [],
17361736
},
1737+
{
1738+
expression: 'sort_by(people, &name)',
1739+
expected: [
1740+
{ age: 10, age_str: '10', bool: true, name: 3 },
1741+
{ age: 20, age_str: '20', bool: true, name: 'a', extra: 'foo' },
1742+
{ age: 40, age_str: '40', bool: false, name: 'b', extra: 'bar' },
1743+
{ age: 30, age_str: '30', bool: true, name: 'c' },
1744+
{ age: 50, age_str: '50', bool: false, name: 'd' },
1745+
],
1746+
},
17371747
])('should support sorty_by() special cases', ({ expression, expected }) => {
17381748
// Prepare
17391749
const data = {
@@ -1778,27 +1788,25 @@ describe('Functions tests', () => {
17781788

17791789
// Assess
17801790
expect(result).toStrictEqual(expected);
1781-
}); */
1782-
/* it.each([
1791+
});
1792+
1793+
it.each([
17831794
{
17841795
expression: 'sort_by(people, &extra)',
1785-
error: 'TypeError: expected (string), received null',
1796+
error:
1797+
'Invalid argument type for function sort_by(), expected "string" but found "null" in expression: sort_by(people, &extra)',
17861798
},
17871799
{
17881800
expression: 'sort_by(people, &bool)',
1789-
error: 'TypeError: unexpected type (boolean)',
1790-
},
1791-
{
1792-
expression: 'sort_by(people, &name)',
1793-
error: 'TypeError: expected (string), received number',
1801+
error:
1802+
'Invalid argument type for function sort_by(), expected "string" but found "boolean" in expression: sort_by(people, &bool)',
17941803
},
17951804
{
17961805
expression: 'sort_by(people, name)',
17971806
error:
1798-
'TypeError: sort_by() expected argument 2 to be type (expression) but received type null instead.',
1807+
'Invalid argument type for function sort_by(), expected "expression" but found "null" in expression: sort_by(people, name)',
17991808
},
18001809
])('sort_by() function special cases errors', ({ expression, error }) => {
1801-
// TODO: see if we can assert the error type as well in sort_by() function special cases errors tests
18021810
// Prepare
18031811
const data = {
18041812
people: [
@@ -1839,7 +1847,8 @@ describe('Functions tests', () => {
18391847

18401848
// Act & Assess
18411849
expect(() => search(expression, data)).toThrow(error);
1842-
}); */
1850+
});
1851+
18431852
/* it.each([
18441853
{
18451854
expression: 'max_by(people, &age)',
@@ -2091,7 +2100,7 @@ describe('Functions tests', () => {
20912100
expect(() => search(expression, data)).toThrow(error);
20922101
});
20932102
*/
2094-
/* it.each([
2103+
it.each([
20952104
{
20962105
description: 'stable sort order',
20972106
expression: 'sort_by(people, &age)',
@@ -2198,7 +2207,7 @@ describe('Functions tests', () => {
21982207

21992208
// Assess
22002209
expect(result).toStrictEqual(expected);
2201-
}); */
2210+
});
22022211

22032212
it.each([
22042213
{

0 commit comments

Comments
 (0)