diff --git a/src/urlBuilder.ts b/src/urlBuilder.ts index 1cbf13f..2d767dc 100644 --- a/src/urlBuilder.ts +++ b/src/urlBuilder.ts @@ -49,6 +49,18 @@ type ParsedFiltersResult = { select: any; }; +const isObject = obj => + typeof obj === 'object' && !Array.isArray(obj) && obj !== null; + +const resolveKeys = (filter: any, keys: Array) => { + let result = filter[keys[0]]; + for (let i = 1; i < keys.length; ++i) { + result = result[keys[i]]; + } + + return result; +}; + export type PostgRestOperator = (typeof postgrestOperators)[number]; export const parseFilters = ( @@ -62,22 +74,44 @@ export const parseFilters = ( Object.keys(filter).forEach(function (key) { // key: the name of the object key + let keyArray = [key]; + let values: Array; + + // in case of a nested object selection in a text field + // RA returns object{ object } structure which is resolved here + // see issue https://github.com/raphiniert-com/ra-data-postgrest/issues/58 + if (key.split('@')[0] !== '' && isObject(filter[key])) { + let innerVal = filter[key]; + + do { + const inner = resolveKeys(filter, keyArray); + const [innerKey] = Object.keys(inner); + + keyArray.push(innerKey); + innerVal = inner[innerKey]; + } while (isObject(innerVal)); + + key = keyArray.join('.'); + values = [innerVal]; + } else { + values = [filter[key]]; + } + const splitKey = key.split('@'); const operation: PostgRestOperator = splitKey.length == 2 ? (splitKey[1] as PostgRestOperator) : defaultListOp; - let values: Array; if (['like', 'ilike'].includes(operation)) { // we split the search term in words - values = filter[key].trim().split(/\s+/); + values = resolveKeys(filter, keyArray).trim().split(/\s+/); } // CASE: Logical operator else if (['or', 'and'].includes(operation)) { // we extract each value entries and make it dot separated string. const { filter: subFilter } = parseFilters( - { filter: filter[key] }, + { filter: resolveKeys(filter, keyArray) }, defaultListOp ); @@ -91,8 +125,6 @@ export const parseFilters = ( // finally we flatten all as single string and enclose with bracket. values = [`(${filterExpressions.join(',')})`]; - } else { - values = [filter[key]]; } values.forEach(value => { @@ -108,7 +140,7 @@ export const parseFilters = ( return `${operation}.${value}`; })(); - // If resulting filter haven't contains the fieldname, then add it. + // If resulting filter doesn't contain the fieldname, then add it. if (result.filter[splitKey[0]] === undefined) { // first operator for the key, if (['and', 'or'].includes(operation)) { diff --git a/tests/urlBuilder/urlBuilder.test.ts b/tests/urlBuilder/urlBuilder.test.ts index edbd2a1..977b1ae 100644 --- a/tests/urlBuilder/urlBuilder.test.ts +++ b/tests/urlBuilder/urlBuilder.test.ts @@ -32,6 +32,14 @@ describe('parseFilters', () => { 'q6@cs': ['foo', 'bar'], 'q7@cd': 'foo', 'q8@cd': ['foo', 'bar'], + q9: { 'foo@ilike': 'bar'}, + q10: { 'foo@like': 'baz qux'}, + q11: { 'foo@gt': 'c'}, + q12: { 'foo@cs': 'bar'}, + q13: { 'foo@cs': ['foo', 'bar']}, + q14: { 'foo@cd': 'bar'}, + q15: { 'foo@cd': ['foo', 'bar']}, + q16: { 'foo': {'bar@cs': ['foo', 'bar']}} } }, 'eq' @@ -46,6 +54,14 @@ describe('parseFilters', () => { q6: 'cs.{foo,bar}', q7: 'cd.{foo}', q8: 'cd.{foo,bar}', + 'q9.foo':'ilike.*bar*', + 'q10.foo': ['like.*baz*', 'like.*qux*'], + 'q11.foo': 'gt.c', + 'q12.foo': 'cs.{bar}', + 'q13.foo': 'cs.{foo,bar}', + 'q14.foo': 'cd.{bar}', + 'q15.foo': 'cd.{foo,bar}', + 'q16.foo.bar': 'cs.{foo,bar}' } }); });