You can read about code-pool concepts on this link.
About this post
In this post, we’ll build a utility to handle adding or updating key-value pairs on an object — cleanly and flexibly! It’s a powerful, reusable tool to include in your projects. We'll also address common edge cases to ensure the final result is robust and production-ready.
Table of Contents
JavaScript Limitations When Setting Key-Value Pairs on an Object
By default, when you set a key-value pair on an object in JavaScript, it first checks if the key exists. If it doesn't, the key is added and the value is assigned. If the key does exist, the old value is simply replaced.
However, in real-world scenarios, you might want more control — such as extending the existing value, appending to it, or ignoring the update entirely. Handling these kinds of cases manually can become repetitive and error-prone, especially when dealing with complex or non-primitive values.
That’s where a reusable utility comes in handy. Let’s explore the utility and its capabilities in the next section.
Utility: setKeyValueOnObject()
Note: I’m going to import the isPlainObject
utility we discussed in this post.
Here’s the isPlainObject
code:
export const isPlainObject = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value);
And here’s the JavaScript utility to properly handle both add and update operations!
import { isPlainObject } from './isPlainObject';
type UpdateMode = 'append' | 'replace' | 'extend' | 'ignore';
interface Options { decodeURI?: boolean; onConflict?: UpdateMode; flatArraysOnAppend?: boolean; removeArrayDuplicatedValues?: boolean; }
export const setKeyValueOnObject = < T extends object, K extends keyof T, V = unknown >( obj: T, key: K, value: V, options: Options = {} ): T & Record<K, V> => { const { decodeURI = false, onConflict = 'replace', flatArraysOnAppend = false, removeArrayDuplicatedValues = false, } = options;
const newValue = decodeURI && typeof value === 'string' ? decodeURIComponentSafe(value) : value;
const hasKey = key in obj; const current = obj[key as keyof T] as V | undefined;
if (hasKey) { if (onConflict === 'ignore') return obj as T & Record<K, V>;
if (current !== newValue) { if (onConflict === 'append' && Array.isArray(current)) { let appended = [...current, newValue]; if (flatArraysOnAppend) appended = appended.flat(); if (removeArrayDuplicatedValues) appended = Array.from(new Set(appended));
return { ...obj, [key]: appended } as T & Record<K, V>; }
if ( onConflict === 'extend' && isPlainObject(current) && isPlainObject(newValue) ) { return { ...obj, [key]: { ...current, ...newValue } } as T & Record<K, V>; }
if (onConflict === 'replace') { return { ...obj, [key]: newValue } as T & Record<K, V>; } }
return obj as T & Record<K, V>; }
return { ...obj, [key]: newValue } as T & Record<K, V>; };
const decodeURIComponentSafe = (v: string): string => { try { return decodeURIComponent(v); } catch { return v; } };
Explanation:
decodeURI
is useful when working with URL search parameters.onConflict
lets you control what happens if the key already exists. Options include:- 'replace' (default): overwrite the existing value.
- 'append': merge values into an array.
- 'extend': shallow-merge plain objects.
- 'ignore': skip the update if the key exists.
flatArraysOnAppend
is helpful when the new value is an array and you want to flatten the result. For example: if arr: [1] is updated with [2, 3], the result will be arr: [1, 2, 3].removeArrayDuplicatedValues
ensures that appended arrays don’t contain duplicate values.
You can find the code and useful JSDoc at this GitHub link.