My favorite workhorse for this type of thing is the following little function.
function transform(obj, fn) {
function transformer(x) {
if (!x || typeof x !== 'object')
return x;
if (Array.isArray(x))
return x
.map((v, k) => fn(k, v, transformer))
.filter(x => x)
.reduce((a, x) => (a[x[0]] = x[1], a), []);
return Object.fromEntries(
Object.entries(x)
.map(([k, v]) => fn(k, v, transformer))
.filter(x => x));
}
return transformer(obj);
}
How does this work? It walks your object recursively and invokes a callback for each key-value (or index-value) pair. The callback receives three arguments: a key, a value and a "carry on" function and is supposed to return a new key-value pair. In the callback you can choose to continue recursion by calling the "carry on" function on the value, but you don't have to. If the callback returns undefined, the pair is eliminated.
Applied to your question, the callback might look like this:
REPLACE = {
'x': 'key1',
'y': 'nested1',
'y.z': 'nested1.key1',
}
stack = [];
result = transform(inObj, (key, val, carryOn) => {
stack.push(key)
val = carryOn(val)
let path = stack.join('.')
if (path in REPLACE)
key = REPLACE[path].split('.').pop()
stack.pop()
return [key, val]
})
You might want to wrap the whole thing into a function to avoid the global stack.