From e87da21c38773a182cc2334cbf80053384cec790 Mon Sep 17 00:00:00 2001 From: Zia Ul Rehman Date: Wed, 11 Sep 2019 03:04:39 -0700 Subject: [PATCH 1/2] Capability to search in nested arrays of objects --- source/Search.js | 70 ++++++++++++++++++++++------------ source/Search.test.js | 18 ++++++++- source/getNestedFieldValues.js | 30 +++++++++++++++ 3 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 source/getNestedFieldValues.js diff --git a/source/Search.js b/source/Search.js index fc1bb4c..088d4ce 100644 --- a/source/Search.js +++ b/source/Search.js @@ -1,5 +1,6 @@ // @flow import getNestedFieldValue from './getNestedFieldValue'; +import getNestedFieldValues from './getNestedFieldValues'; import { PrefixIndexStrategy } from './IndexStrategy/index'; import { LowerCaseSanitizer } from './Sanitizer/index'; @@ -157,6 +158,44 @@ export class Search { return this._searchIndex.search(tokens, this._documents); } + /** + * @param doc + * @param fieldValue Value to index + * @private + */ + indexDocumentsValue_(doc : Object, uid : string, fieldValue : string) : void { + this._initialized = true; + + var indexStrategy = this._indexStrategy; + var sanitizer = this._sanitizer; + var searchIndex = this._searchIndex; + var tokenizer = this._tokenizer; + var uidFieldName = this._uidFieldName; + + if ( + fieldValue != null && + typeof fieldValue !== 'string' && + fieldValue.toString + ) { + fieldValue = fieldValue.toString(); + } + + if (typeof fieldValue === 'string') { + var fieldTokens = tokenizer.tokenize(sanitizer.sanitize(fieldValue)); + + for (var fti = 0, numFieldValues = fieldTokens.length; fti < numFieldValues; fti++) { + var fieldToken = fieldTokens[fti]; + var expandedTokens = indexStrategy.expandToken(fieldToken); + + for (var eti = 0, nummExpandedTokens = expandedTokens.length; eti < nummExpandedTokens; eti++) { + var expandedToken = expandedTokens[eti]; + + searchIndex.indexDocument(expandedToken, uid, doc); + } + } + } + } + /** * @param documents * @param _searchableFields Array containing property names and paths (lists of property names) to nested values @@ -182,36 +221,17 @@ export class Search { } for (var sfi = 0, numSearchableFields = _searchableFields.length; sfi < numSearchableFields; sfi++) { - var fieldValue; + var fieldValue, fieldValues; var searchableField = _searchableFields[sfi]; if (searchableField instanceof Array) { - fieldValue = getNestedFieldValue(doc, searchableField); + fieldValues = getNestedFieldValues(doc, searchableField); + for (var i = 0; i < fieldValues.length; i++) { + this.indexDocumentsValue_(doc, uid, fieldValues[i]); + } } else { fieldValue = doc[searchableField]; - } - - if ( - fieldValue != null && - typeof fieldValue !== 'string' && - fieldValue.toString - ) { - fieldValue = fieldValue.toString(); - } - - if (typeof fieldValue === 'string') { - var fieldTokens = tokenizer.tokenize(sanitizer.sanitize(fieldValue)); - - for (var fti = 0, numFieldValues = fieldTokens.length; fti < numFieldValues; fti++) { - var fieldToken = fieldTokens[fti]; - var expandedTokens = indexStrategy.expandToken(fieldToken); - - for (var eti = 0, nummExpandedTokens = expandedTokens.length; eti < nummExpandedTokens; eti++) { - var expandedToken = expandedTokens[eti]; - - searchIndex.indexDocument(expandedToken, uid, doc); - } - } + this.indexDocumentsValue_(doc, uid, fieldValue); } } } diff --git a/source/Search.test.js b/source/Search.test.js index 94fca10..bb7737b 100644 --- a/source/Search.test.js +++ b/source/Search.test.js @@ -1,7 +1,7 @@ import { Search } from './Search'; describe('Search', function() { - var documentBar, documentBaz, documentFoo, nestedDocumentFoo, search; + var documentBar, documentBaz, documentFoo, nestedDocumentFoo, nestedArrayDocumentFoo, search; var validateSearchResults = function(results, expectedDocuments) { expect(results.length).toBe(expectedDocuments.length); @@ -42,6 +42,15 @@ describe('Search', function() { title: 'nested foo' } } + nestedArrayDocumentFoo = { + uid: 'foo', + title: 'foo', + description: 'Is kung foo the same as kung fu?', + writers: [ + { name: 'ABC' }, + { name: 'xyz' } + ] + }; }); it('should throw an error if instantiated without the :uidFieldName parameter', function() { @@ -115,6 +124,13 @@ describe('Search', function() { validateSearchResults(search.search('nested foo'), [nestedDocumentFoo]); }); + it('should index nested arrays document properties', function() { + search.addIndex(['writers', '[]', 'name']); + search.addDocument(nestedArrayDocumentFoo); + + validateSearchResults(search.search('ABC'), [nestedArrayDocumentFoo]); + }); + it('should gracefully handle broken property path', function() { search.addIndex(['nested', 'title', 'not', 'existing']); search.addDocument(nestedDocumentFoo); diff --git a/source/getNestedFieldValues.js b/source/getNestedFieldValues.js new file mode 100644 index 0000000..06013da --- /dev/null +++ b/source/getNestedFieldValues.js @@ -0,0 +1,30 @@ +/** + * Find and return nested object values. + * + * @param object to crawl + * @param path Property path + * @returns {any} + */ +export default function getNestedFieldValues(object : Object, path : Array) { + path = path || []; + object = object || {}; + + var values = []; + var value = object; + // walk down the property path + for (var i = 0; i < path.length; i++) { + if (path[i] == "[]") { + for (var j=0; j < value.length; j++) { + values = values.concat(getNestedFieldValues(value[j], path.slice(i + 1, path.length))); + } + return values; + } else { + value = value[path[i]]; + if (value == null || value == undefined) { + return []; + } + } + } + + return [value]; +} From 24b770da00c7660af9e7ece02c9bb36f2ce80bc3 Mon Sep 17 00:00:00 2001 From: Zia Ul Rehman Date: Wed, 11 Sep 2019 05:20:31 -0700 Subject: [PATCH 2/2] Add more test cases --- source/Search.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/source/Search.test.js b/source/Search.test.js index bb7737b..9f966e2 100644 --- a/source/Search.test.js +++ b/source/Search.test.js @@ -49,6 +49,13 @@ describe('Search', function() { writers: [ { name: 'ABC' }, { name: 'xyz' } + ], + deeplyNested: [ + { + writers: [ + { name: 'deeply nested name'} + ] + } ] }; }); @@ -131,6 +138,20 @@ describe('Search', function() { validateSearchResults(search.search('ABC'), [nestedArrayDocumentFoo]); }); + it('should index deeply nested arrays document properties', function() { + search.addIndex(['deeplyNested', '[]', 'writers', '[]', 'name']); + search.addDocument(nestedArrayDocumentFoo); + + validateSearchResults(search.search('deeply nested'), [nestedArrayDocumentFoo]); + }); + + it('should not give false results for deeply nested arrays document properties', function() { + search.addIndex(['deeplyNested', '[]', 'writers', '[]', 'name']); + search.addDocument(nestedArrayDocumentFoo); + + validateSearchResults(search.search('something not findable'), []); + }); + it('should gracefully handle broken property path', function() { search.addIndex(['nested', 'title', 'not', 'existing']); search.addDocument(nestedDocumentFoo);