Skip to content

Commit 3720fc5

Browse files
committed
fix: support non-primitive types
1 parent c9700e4 commit 3720fc5

File tree

3 files changed

+65
-13
lines changed

3 files changed

+65
-13
lines changed

src/compile.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,46 @@ describe('customization', () => {
207207
expect(go({ a: 2 }, ['aboutEq', ['get', 'a'], 2], options)).toEqual(true)
208208
expect(go({ a: 2 }, ['aboutEq', ['get', 'a'], '2'], options)).toEqual(true)
209209
})
210+
211+
test('should use valueOf() in case of non-primitive types in all operators', () => {
212+
class BigNumber {
213+
public _value: string
214+
215+
constructor(public value: string) {
216+
this._value = value
217+
}
218+
219+
valueOf: () => number = () => Number.parseFloat(this.value)
220+
}
221+
222+
const three = new BigNumber('3')
223+
const six = new BigNumber('6')
224+
const two = new BigNumber('2')
225+
const anotherTwo = new BigNumber('2')
226+
227+
// Test whether we can use our BigNumber.valueOf()
228+
// @ts-ignore
229+
expect(two + three).toEqual(5)
230+
231+
expect(go(null, ['eq', three, two] as JSONQuery)).toEqual(false)
232+
expect(go(null, ['eq', two, anotherTwo] as JSONQuery)).toEqual(true)
233+
expect(go(null, ['ne', three, two] as JSONQuery)).toEqual(true)
234+
expect(go(null, ['ne', two, anotherTwo] as JSONQuery)).toEqual(false)
235+
236+
expect(go(null, ['gt', three, two] as JSONQuery)).toEqual(true)
237+
expect(go(null, ['gte', three, two] as JSONQuery)).toEqual(true)
238+
expect(go(null, ['lt', three, two] as JSONQuery)).toEqual(false)
239+
expect(go(null, ['lte', three, two] as JSONQuery)).toEqual(false)
240+
241+
expect(go(null, ['add', three, two] as JSONQuery)).toEqual(5)
242+
expect(go(null, ['subtract', three, two] as JSONQuery)).toEqual(1)
243+
expect(go(null, ['multiply', three, two] as JSONQuery)).toEqual(6)
244+
expect(go(null, ['divide', six, two] as JSONQuery)).toEqual(3)
245+
expect(go(null, ['pow', three, two] as JSONQuery)).toEqual(9)
246+
expect(go(null, ['mod', three, two] as JSONQuery)).toEqual(1)
247+
248+
expect(go([three, two], ['sort'] as JSONQuery)).toEqual([two, three])
249+
})
210250
})
211251

212252
test('should validate the compile test-suite against its JSON schema', () => {

src/functions.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { compile } from './compile'
2-
import { isArray, isEqual } from './is'
2+
import { getValueOf, isArray, isEqual, typeOf } from './is'
33
import type {
44
Entry,
55
FunctionBuilder,
@@ -31,12 +31,12 @@ const sortableTypes = { boolean: 0, number: 1, string: 2 }
3131
const otherTypes = 3
3232

3333
const gt = (a: unknown, b: unknown) =>
34-
typeof a === typeof b && (typeof a) in sortableTypes ? a > b : false
34+
typeOf(a) === typeOf(b) && typeOf(a) in sortableTypes ? a > b : false
3535

3636
const gte = (a: unknown, b: unknown) => isEqual(a, b) || gt(a, b)
3737

3838
const lt = (a: unknown, b: unknown) =>
39-
typeof a === typeof b && (typeof a) in sortableTypes ? a < b : false
39+
typeOf(a) === typeOf(b) && typeOf(a) in sortableTypes ? a < b : false
4040

4141
const lte = (a: unknown, b: unknown) => isEqual(a, b) || lt(a, b)
4242

@@ -143,18 +143,23 @@ export const functions: FunctionBuildersMap = {
143143
function compare(itemA: unknown, itemB: unknown) {
144144
const a = getter(itemA)
145145
const b = getter(itemB)
146+
const typeA = typeOf(a)
147+
const typeB = typeOf(b)
146148

147149
// Order mixed types
148-
if (typeof a !== typeof b) {
149-
const aIndex = sortableTypes[typeof a] ?? otherTypes
150-
const bIndex = sortableTypes[typeof b] ?? otherTypes
150+
if (typeA !== typeB) {
151+
const aIndex = sortableTypes[typeA] ?? otherTypes
152+
const bIndex = sortableTypes[typeB] ?? otherTypes
151153

152154
return aIndex > bIndex ? sign : aIndex < bIndex ? -sign : 0
153155
}
154156

155157
// Order two numbers, two strings, or two booleans
156-
if ((typeof a) in sortableTypes) {
157-
return a > b ? sign : a < b ? -sign : 0
158+
if (typeA in sortableTypes) {
159+
const _a = getValueOf(a)
160+
const _b = getValueOf(b)
161+
162+
return _a > _b ? sign : _a < _b ? -sign : 0
158163
}
159164

160165
// Leave arrays, objects, and unknown types ordered as is

src/is.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
export const isArray = <T>(value: unknown): value is T[] => Array.isArray(value)
22

33
export const isObject = (value: unknown): value is object =>
4-
value !== null && typeof value === 'object' && !isArray(value)
4+
value !== null && typeof value === 'object' && value.constructor === Object
55

66
export const isString = (value: unknown): value is string => typeof value === 'string'
77

88
// source: https://stackoverflow.com/a/77278013/1262753
99
export const isEqual = <T>(a: T, b: T): boolean => {
10-
if (a === b) {
10+
const _a = getValueOf(a)
11+
const _b = getValueOf(b)
12+
13+
if (_a === _b) {
1114
return true
1215
}
1316

14-
const bothObject = a !== null && b !== null && typeof a === 'object' && typeof b === 'object'
17+
const bothObject = (isObject(_a) || isArray(_a)) && (isObject(_b) || isArray(_b))
1518

1619
return (
1720
bothObject &&
18-
Object.keys(a).length === Object.keys(b).length &&
19-
Object.entries(a).every(([k, v]) => isEqual(v, b[k as keyof T]))
21+
Object.keys(_a).length === Object.keys(_b).length &&
22+
Object.entries(_a).every(([k, v]) => isEqual(v, _b[k]))
2023
)
2124
}
25+
26+
export const typeOf = (value: unknown): string => typeof getValueOf(value)
27+
28+
export const getValueOf = (value: unknown): unknown => (value ? value.valueOf() : value)

0 commit comments

Comments
 (0)