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
*/
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 isfalse
. -
cloneChild
: A boolean that indicates if the children object should be cloned or just referenced, default istrue
.
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
}
}
}
*/
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
}
}
}
*/
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: {}
}
}
}
*/
Things you need to know before you use it
- When
cloneParent
is set tofalse
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 totrue
the function will return a clone of the parent object, but the original object will be unaffected. - When
clonechildren
is set totrue
it will create a clone of the object and append it. But if you set it tofalse
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
objectproducts.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)