DEV Community

Glib Zaycev
Glib Zaycev

Posted on

Best way to clean JS objects from empty values

Hi there! This is my first try at posting something meaningful and related to the everyday developer experience. I hope you'll like it, and thanks for reading!


Recently, I faced the issue when I had to clean the json object from the empty fields. This is a pretty common case and can be found in various places, like formatting API responses, preparing API requests, and working with reusable forms.

So I created the method to format such objects and then wondered what is the best way to do it was. Let's look at the example. I have a simple user object, but I really don't need all the information about him.

const person = {
  id: "",
  name: "Glib",
  age: 30,
  group: "",
};
Enter fullscreen mode Exit fullscreen mode

That was the initial implementation of the method to clean this object.

const removeEmptyValues = (object) =>
  Object.fromEntries(
    Object.entries(object)
      .filter(([_, value]) => value)
  )
Enter fullscreen mode Exit fullscreen mode

Totally works.
What I love about it is that it is a one-liner.
What I hate about it is that it is, eh, a one-liner. Which implies it doesn't have all the checks and maybe even doesn't have the best performance (not necessarily, but probably). Spolier: It really is not good.

The main problem here is not even the performance, but the fact that it will fail with the first not object passed to it as an argument.

So this was the better version:

const removeEmptyValues = (object) =>
typeof (!!object &&
          object === "object" && 
          !Array.isArray(object))
    ? Object.fromEntries(
        Object.entries(object)
          .filter(([_, value]) => value)
        )
    : object;
Enter fullscreen mode Exit fullscreen mode

And even though it's still a one-liner, the way this code looks make me wanna leave coding for working at slaughterhouse. Primarily not because of the ternary but because of, well, all things together.

Now it's much better functionally, though. It's going to make a check and extract the object to clean it up if it's truly an object, and just return a passed value if it's something else.

It could throw the error to make it more informative, but let's keep this talk at the current level of abstraction. Let's just pretend it's okay as it is, and if you want, you can add your error handling the way you like it.

I ended up separating logic a bit, making this method dirtier by nature but much cleaner for the reader, and just extracting the check and the actual logic into their own little methods isolated in the scope of the file that's going to store all 3 methods:

const isObject = (object) =>
  typeof !!object && object === "object" && !Array.isArray(object));

const cleanObject = (object) =>
  Object.fromEntries(
    Object.entries(object)
      .filter(([_, value]) => value)
    );

export const removeEmptyValues = (object) =>
  isObject(object) ? cleanObject(object): object;

Enter fullscreen mode Exit fullscreen mode

Now I actually quite like it. It's readable, responsibilities are separated and extracted, and it looks like most possible failure cases are resolved.

However...

It's slow. Simple as that, just looking at this chain of calls to Object.entries() and Object.fromEntries() makes me want to throw the monitor out of the window.

So I've decided to make a simple experiment with performance and created two more methods with the same functionality but different logic. Here we go:

const removeEmptyValuesForLoop = (object) => {
  if (!isObject(object)) returns;

  for (let key in object) {
    if (!object[key]) delete object[key];
  }
  return object;
};

const removeEmptyObjectsByKeys = (object) => {
  if (!isObject(object)) returns;

  let newObject = {};
  Object.keys(object).forEach((key) => {
    if (object[key]) {
      newObject[key] = object[key];
    }
  });
  return object;
};
Enter fullscreen mode Exit fullscreen mode

Leaving aside the initial type check, you can clearly see the approaches:

  • In the first one, we just go through the object in a for loop and delete the properties that are empty.
  • In the second one, we are building the new object from scratch.

To be honest, I kind of hate the idea of the last one, and I thought it would have the worst performance with big objects. However, I was a bit surprised when I ran performance tests.

For it, I created a little helper function that just iterates through the same function again and again for N times:

const measure = (fn, times) => {
  const start = performance.now();
  for (let i = 0; i < times; i++) {
    fn();
  }
  const finish = performance.now();
  return (finish-start).toFixed(2);
};
Enter fullscreen mode Exit fullscreen mode

And ran it for 1 000 000 times for each of 3 methods:

Cleaning objects from empty values comparison for 1mln calls

As we thought, formatting objects with the Object class methods was the slowest one.

Going through the loop was much faster.
And building a new object from the old one showed the best performance!

I've tried to increase the number of fields in the object, but in a reasonably larger range, it kept its best performance even with hundreads of fields.

That was a pretty interesting little experiment with raw JS to understand the best way to clean objects from empty values.

As for me, I'll still probably prefer the #1 solution even though it's slower, because in my use cases I only need to run it once for a long time, and after all the refactoring, it looks readable and neat.

However, if you need to process big chunks of data, you definitely should choose a lower-level approach, the delete operator, or build the object from scratch.

Thanks for reading this far; hopefully it was helpful!

I'd love to hear any feedback!

Top comments (2)

Collapse
 
onlinemsr profile image
Raja MSR

Thanks for sharing!

You can use sub-titles to separate contents specific to the context. Instead of writing custom code for performance test, you can try jsperf.app/

Collapse
 
baypanic profile image
Glib Zaycev • Edited

Thank you!

Thanks for advice, I'll try that platform out!