DEV Community

Cover image for Append object to path - Util function #3
Schemetastic (Rodrigo)
Schemetastic (Rodrigo) Subscriber

Posted on • Edited on

Append object to path - Util function #3

In the past days I've shared with you a few JS utilities, to loop objects recurrently, and also to get data in a nested object or to verify an object path programmatically.

In today's utility, you'll get a function to append another object inside an object at a specific path.

appendObj(parentObj, childObj, path = [], cloneParent = false, cloneChild = true)

/**
 * 
 * @param {Object} parentObj the object itself
 * @param {string[]} path the sequence to append
 * @param {Object} dataTo the data you want to insert
 * @returns {Object} the passed object with a the appended object
 */
function appendObj(parentObj, childObj, path = [], cloneParent = false, cloneChild = true) {
    // Verify the parameters
    if (getType(parentObj) != "object") throw TypeError("The `parentObj` argument is not an object")
    if (getType(childObj) != "object") throw TypeError("The `childObj` argument is not an object")
    if (getType(path) != "array") throw TypeError("The `path` argument is not an array")
    if (getType(cloneParent) != "boolean") throw TypeError("The `cloneParent` argument is not a boolean")
    if (getType(cloneChild) != "boolean") throw TypeError("The `cloneChild` argument is not a boolean")

    // clone the parent object
    if (cloneParent) parentObj = structuredClone(parentObj)

    // clone the child object to avoid shallow copies
    if (cloneChild) childObj = structuredClone(childObj)

    // If the path is empty assign it at the root
    if (path.length == 0) {
        Object.assign(parentObj, childObj)
        return parentObj;
    }

    // Link a reference to the parent object
    let objRef = parentObj;

    // Create path and then assign the object
    path.forEach((item, index) => {
        if (!Object.keys(objRef).includes(item)) objRef[item] = {}
        if (index < path.length - 1) {
            objRef = objRef[item]
        } else {
            if (getType(objRef[item]) != "object") objRef[item] = {}
            objRef = objRef[item]
            Object.assign(objRef, childObj);
        }

    });

    // return the object
    return parentObj;
}

/**
 * A method to detect data types more accurately
 * Credits: Chris Ferdinandi, https://gomakethings.com/
 * @param {*} data the data to be verified
 * @returns {String} the data type
 */
export function getType(data) {
  return Object.prototype.toString.call(data).toLowerCase().slice(8, -1)
}

/**
 * License: MIT, https://opensource.org/license/mit
 * Copyright (c) 2024 Rodrigo Isaias Calix
 */

Enter fullscreen mode Exit fullscreen mode

How to use it?

The function requires at least 3 parameters, and 2 optional, in this order:

  • parentObj: This is the main object where you want to append another object
  • childObj: The object you want to append
  • path: a strings array that indicates the path to append the object
  • cloneParent: A boolean that indicates if the parent object you should be cloned or just referenced, default is false.
  • cloneChild: A boolean that indicates if the children object should be cloned or just referenced, default is true.

Usage examples:

With a referenced parent (default)

let products = {
    externalDevices: {
        keyboard: {
            usb: 45,
            bluetooth: 25,
        }
    }
}

const obj = {
    mouses: {
        usb: 60,
        bluetooth: 80
    }
}

appendObj(products, obj, ["externalDevices"]);

console.log(products)
/*
{
    externalDevices: {
        keyboard: {
            usb: 45,
            bluetooth: 25,
        },
        mouses: {
            usb: 60,
            bluetooth: 80
        }
    }
}
*/

Enter fullscreen mode Exit fullscreen mode

As you can see, you don't need to assign anything to the original object variable, because by default the function uses the original object as a reference and adds to it any data.

With a cloned parent

let copyObj = appendObj(products, obj, ["externalDevices"], true);

console.log(products)
/*
{
    externalDevices: {
        keyboard: {
            usb: 45,
            bluetooth: 25,
        }
    }
}
*/

console.log(copyObj)
/*
{
    externalDevices: {
        keyboard: {
            usb: 45,
            bluetooth: 25,
        },
        mouses: {
            usb: 60,
            bluetooth: 80
        }
    }
}
*/
Enter fullscreen mode Exit fullscreen mode

Unlike the previous example, this time the original object doesn't change because it is cloned. You could also, use the products variable to assign it to itself.

Creating an empty object path

appendObj(products, {}, ["externalDevices", "mouses", "bluetooth"]);
console.log(products);

/*
{
    externalDevices: {
        keyboard: {
            usb: 45,
            bluetooth: 25,
        },
        mouses: {
            bluetooth: {}
        }
    }
}
*/

Enter fullscreen mode Exit fullscreen mode

Things you need to know before you use it

  • When cloneParent is set to false the parent object is just referenced, this means that you don't need to assign the object again to the parent, in other words, if you just run the function itself it will automatically add the data to the original object. If it is set to true the function will return a clone of the parent object, but the original object will be unaffected.
  • When clonechildren is set to true it will create a clone of the object and append it. But if you set it to false it will put a shallow copy of it, this means that if the original object has variables in it and the variables change, any reference could reflect that change too, use with caution.
  • If you set the path to an empty array ([]) it will assign the object to the root of the parent object
  • If the path of the object is not defined, it will be created automatically. For example, if in the products object products.devices.audio.microphones "devices" doesn't exist, it will create the path up to "microphones".
  • In the destination of the path, if the destination is an object, the child object will be assigned inside that object along any other data it has, this means that it will override any identical object property inside. If the destination is not an object, it will override the whole value.

If you found this useful, I'll be sharing more content like this on DEV!

You can also find me on X: https://x.com/schemetastic

And remember to save it for later 🔖

Top comments (0)