Writing a Search feature for every single page or section on your website can be really tedious, and even if you create a reusable function, you might end up changing the code multiple times for it to work for new or dynamic types of JSON data.
I first noticed this problem at work, where I had to write and modify my reusable search function to work for new types of "JSON data" or situations where minor changes would require me to handle page-specific search instructions.
For the most part, You'd require to know the keys of the JSON you'll be working with, so let's say you want to implement a search that can... search using First Name, Last Name, Email Address etc your code might look something like this :
If you read the code closely, you'd realize I knew beforehand the keys of the data I'll be working with, and let's presume in the future, the backend changes and email is taken out, or phoneNumber which in this case, the code will break (crash) or the backend includes a new data we'd love to also be able to search with, like age or role, which would result in us changing the code to cater for age etc.
You should already start seeing the problem here so... I decide to come up with a simple algorithm that'll eliminate all these problems by :
- Making sure you don't even have to know or care about the keys
- Still works, even for changing or dynamic JSON data
- Includes combination settings e.g combining first name, last name and middle name in search inputs.
- Prevent your heart from racing when your company/friend(s) is/are testing your application
- Saves you a lot of time and burnout 😎
Okay, now let's get to it, we'll start from the basics then improve along the way.
Version 1
First, the most basic requirement is your array of data and a function that'll feed on it, so if you have :
We'll start the search code by :
- Declaring a search function that takes 3-arguments ( the event (in this case, its the user input), the JSON array, and the function (or variable) that'll receive the result.
After that, we'll fix the code below to prevent the page from refreshing and getting the user input (typed search value)
The second step here would be to loop over the array and get the search result using Array.filter, and what this simply does is loop through the array and do some conditional checks that we'll write, and if the check passes, it'll return the specific data that passes the test. Check the MDN docs for an in-depth information.
So we'll have something like :
Now, it's all starting to look a lot like the old code I complained about, but here comes the trick of this whole saga.
If you remember I said you won't have to worry about knowing the Keys of the JSON data? (`{"Key" : "value"}), well... we're in luck because there's a function for getting the keys (even values) of an object in JavaScript.
We'll be using the Object.keys(yourJSONArray)
function, which returns an array of Keys we can loop over. Check the MDN Docs for an in-depth explanation. Then we can perform a forEach on the returned array (as you might have guessed), but in our case, forEach
(or even a for-loop
) is not an option as we'll be using Array.some and I'll explain why (because I think it's important to note), but first, take a look at the code at this stage.
If you look through the code again, you'll notice that Array.filter
returns the data that passes our conditional check, and we're doing the checks on the Object.keys
but in this case, we want to stop/terminate the loop immediately we find a matching value. What I mean is, if you type Dev in your search field and the code is currently looping over the first data in the Object:
The checks we'll do here is computing if the current Object Value e.g {"key": "value", "key": "value"}
includes the search input, so if the user types Dev in the search box, it'll match firstName and emailAddress and if we have any other value with the word "Dev" it'll match that too, but here's the thing about Array.some
, it'll stop looping once we find the first matching value (firstName) and that's why we're not using forEach because we'd have to manually write more code to make it work and we lowkey can't stop a forEach loop (let me know if am wrong).
Let's move forward. The code and conditions we'll write in the Array.some() will :
-
Get the current value of the Object, and since we don't know the keys beforehand, we'll use
data[key]
which allows us to get the values of objects with dynamic keys (applies in our case). -
Convert the value to a string using JSON.stringify, and it'll look something like :
JSON.stringify(data[key])
-
Convert the value to lower-case because we've also converted the user input to lowercase
const input = event.target.value.toLowerCase();
Note: "A" is not equal to "a" and that's why we're comparing both the input and the value at a lower-case level
-
Trim out any trailing white spaces using
.trim()
. This will eliminate white spaces at the start or end. For example, " name" or "name " is not equal to "name" and that's why we remove any trailing whitespace. And lastly - Check if it includes the search input value using
.includes(inputValue)
- pass the result to a function/variable
Let's take a look at the code to get more clear.
At this point, this code is almost ready to ship because right now it works but we can make it better.
Version 2
Why version 2? I'll explain why, if you take a look at the data again, you'll notice that we have "firstName" and "lastname", we could even have "middleName" later in the future. But currently, our code cannot search in combinations of first-and-last-name, it can only match one at a time.
So... what if we could find a way to get around this? Well... there is a way, and we'll do that by including a fourth argument called "combinations" (or you could call it anything you want), so we'll have something like :
The code I wrote at work to combine searching with "firstName" and "lastname" looks like this :
and you can see that it looks ugly, and I had to also know the keys of the object.
Here, we'll allow the user or caller of our function to decide what combinations they want and in what order they want it because think about it, what if they want it in the order: "lastName"-"firstName" or "middleName"-"firstName" or "firstName"-"lastName"-"middleName"? Exactly!
Our implementations will be slightly different and we'll begin by looping over the combinations passed by the caller or user of our function/algorithm.
In the .filter()
function we'll fix in our combination logic
Then we'll loop over the combinations and add them in the order that the user of our algorithm wants.
Our current code altogether looks like this:
Good, we made it this far, but... wait a min, something's not quite right about this code 🤔. What's wrong with it? Well... it's full of bugs at the moment, lol. And we'll fix them, but first, what are the obvious bugs am talking about? Here's a list
- Bug NO.1
First, I said we don't have to know the key's right? But then am giving the user of our algorithm the ability to provide Keys of the data they want to combine, and what's wrong with this? Well... what if they supplied a key that's not even existent in the JSON data? Bugs! We'll fix it.
In JavaScript, we can check if a key exists in an object by using Object.hasOwnProperty("someKey"). In other words, data.hasOwnProperty("firstName")
will return true
and data.hasOwnProperty("non-existent-key")
will return false
Our improved code should look like this:
Are we done? Nope! Am sorry but we ain't.
- Bug NO.2
let's take a moment to think about what would happen if the user never passes a value for objectData and combinations. There's going to be a problem because we're always trying to perform some actions on these arguments, and we can fix the code by providing a default value.
So if we forget to pass a value for these arguments, we'll still be fine, but how do we do that? We do that by providing an empty array as a default value:
Great! We made it even further, are we done? Yea, we are, but... Maybe we ain't 😎.
There's one more thing I'd like to point out about this algorithm. In JSON data, sometimes, values can come back as null, undefined, true or false, you might have seen this a lot if you've consumed some backend data in the past or current.
So, if a user search the word null, undefined, true or false every object/data that has a the value null or undefined etc will show up.
At my workplace, I allowed the search of false and true but not null or undefined because the API I was working with mostly had an {isActive: true}
or {isActive: false}
key-values, which allows me to search users that are currently activated/deactivated.
I just type "true" or "false" and that does the magic
Version 3
My current code at the time of writing this blog is:
For someone that might want to use the code in a search input will do something like:
And they won't have to worry about what changes about the student data in the future.
Plus even if you're consuming an API with random JSON data (Never seen this happen though) like say, data of persons, and robots, and cat names etc... you get the gist?
you won't have to worry, the search still stands.
The only new thing in my previous snippet (Version 3) is the &&
(and) ||
operator and what &&
simply means is, execute the code on the right if the code to the left (before the &&) is true.
||
simply means, do what's on the left if the condition is true Or do what's on the right if what's on the left is false. I don't feel I should talk much about these operators, if you're not familiar with it, you can look them up, it's no big deal but... it really does save you a lot of typing.
Conclusion
There's no denying the fact that every code can be made better, and if you ever feel you want to improve on the code, or would like to create a say... some React or JS library specifically for "searching" that does more than the 1-thing we learnt here. Feel free to Clone OR Contribute to the GitHub repo.
If you do make a library that gives people that super-power, I'd like to feature you on this blog.
Cheers 🥂 and thanks for reading 😎.
Top comments (9)
What if you have nested objects?
Good question, in that case, another search would have to be implemented on the nested object, something like:
topLevelData.nestedObject
and this would pass for our search array.Hope I've answered your question Haidar?
Yes but not really scalable. What if you have more than 3 level of nesting. I think you could improve your function a bit
There's a library called fusejs that flattens out objects with nested arrays. It checks the type of each value and if array recurses. All in order to create one big flat object you can search in.
Thanks for sharing John.
You might find fusejs interesting as a search library - the "flattening" process is part of how it searches rather than main function.
For the similar task I i tried to solve it by considering the json object as a string and then traversing the cursor position for each search result.
Try here
d2x8qs.csb.app/
Source code: github.com/abdheshnayak/search-in-...
You do object.values on the json items. Then do stringify on the item values. Than call includes on that string.
Not sure about performance though.
For the similar task I i tried to solve it by considering the json object as a string and then traversing the cursor position for each search result.
Try here
d2x8qs.csb.app/
Source code: github.com/abdheshnayak/search-in-...