👋 Say Goodbye to Spread Operator: Use Default Composer

javascript node react

When working with objects in JavaScript, it is common to need to set default values for empty strings/objects/arrays, null, or undefined properties. When dealing with nested objects, this can become even more complicated and require complex programming logic. However, with the "default-composer" library, this task becomes simple and easy.

What is "default-composer"?

"default-composer" is a lightweight (~300B) JavaScript library that allows you to set default values for nested objects. The library replaces empty strings/arrays/objects, null, or undefined values in an existing object with the defined default values, which helps simplify programming logic and reduce the amount of code needed to set default values.

Default Composer logo
Default Composer logo

Benefits over Spread Operator and Object.assign

While ...spread operator and Object.assign() can also be used to set default values for objects, "default-composer" provides several benefits over these methods.

Imagine we have this original object:

const original = {
  name: '',
  score: null,
  address: {
    street: '',
    city: '',
    state: '',
    zip: '',
  },
  emails: [],
  hobbies: [],
  another: 'anotherValue',
}

And these are the defaults:

const defaults = {
  name: 'John Doe',
  score: 5,
  address: {
    street: '123 Main St',
    city: 'Anytown',
    state: 'CA',
    zip: '12345',
  },
  emails: ['john.doe@example.com'],
  hobbies: ['reading', 'traveling'],
}

We want to merge these objects replacing the original values that are "", null, [], undefined and {} to the default value. So the idea is to get:

console.log(results)
/**
 * {
 * "name": "John Doe",
 * "score": 5,
 * "address": {
 *   "street": "123 Main St",
 *   "city": "Anytown",
 *   "state": "CA",
 *   "zip": "12345"
 * },
 * "emails": [
 *   "john.doe@example.com"
 * ],
 * "hobbies": [
 *   "reading",
 *   "traveling"
 * ],
 * "another": "anotherValue"
 **/

Probably with spread operator we will have to do something like that:

const results = {
  ...defaults,
  ...original,
  name: original.name || defaults.name,
  score: original.score ?? defaults.score, // "??" beacause 0 is valid
  address: {
    ...defaults.address,
    ...original.address,
    street: original.address.street || defaults.address.street,
    city: original.address.city || defaults.address.city,
    state: original.address.state || defaults.address.state,
    zip: original.address.zip || defaults.address.zip,
  },
  emails: original.emails.length ? original.emails : defaults.emails,
  hobbies: original.hobbies.length ? original.hobbies : defaults.hobbies,
}

and with Object.assign something like this:

const results = Object.assign({}, defaults, original, {
  name: original.name || defaults.name,
  score: original.score ?? defaults.score, // "??" beacause 0 is valid
  address: Object.assign({}, defaults.address, original.address, {
    street: original.address.street || defaults.address.street,
    city: original.address.city || defaults.address.city,
    state: original.address.state || defaults.address.state,
    zip: original.address.zip || defaults.address.zip,
  }),
  emails: original.emails.length ? original.emails : defaults.emails,
  hobbies: original.hobbies.length ? original.hobbies : defaults.hobbies,
})

Maintaining this can be very tidious, especially with huge, heavily nested objects.

Headache
Headache...

With defaultComposer we could only use this:

import defaultComposer from 'default-composer' // 300B
// ...
const results = defaultComposer(defaults, original)

Easier to maintain, right? 😉

Easier
Happier an easier

What happens if in our project there is a special property that works differently from the others and we want another replacement logic? Well, although defaultComposer has by default a configuration to detect the defautable values, you can configure it as you like.

import { defaultComposer, setConfig } from 'default-composer'

setConfig({
  // This function is executed for each value of each key that exists in
  // both the original object and the defaults object.
  isDefaultableValue: (
    // - key: key of original or default object
    // - value: value in the original object
    // - defaultableValue: pre-calculed boolean, you can use or not,
    //   depending if all the rules of the default-composer library are correct
    //   for your project or you need a totally different ones.
    { key, value, defaultableValue }
  ) => {
    if (key === 'rare-key') {
      return defaultableValue || value === 'EMPTY'
    }

    return defaultableValue
  },
})

Conclusions

I've introduced the "default-composer" library as a solution for setting default values for nested objects in JavaScript.

The library is lightweight and provides more concise and easier-to-read code than the spread operator and Object.assign methods. It also offers more granular control over which properties should be set to default values.

In this article I provide examples of how to use the library and how it simplifies the code for maintaining nested objects.

Finally, I explain how the library can be configured to handle special cases where a different replacement logic is required. Overall, "default-composer" is a useful library for simplifying the task of setting default values for nested objects in JavaScript.

Discuss on Dev.toDiscuss on TwitterEdit on GitHub

Subscribe to new posts! 📩

More...
Power of Partial Prerendering with Bun

Power of Partial Prerendering with Bun

Creating Scalable and Reusable React Components

Creating Scalable and Reusable React Components

🏝️ i18n translations in Next.js 13's app-dir for server/client components 🌊

🏝️ i18n translations in Next.js 13's app-dir for server/client components 🌊

Teaful DevTools Released!

Teaful DevTools Released!