Get all paths to a specific key in a deeply nested object - javascript

How do i recursively search for a specific key in a deeply nested object.
For example:
let myObject = {a: {k:111, d:3}, b:"2", c: { b: {k: 222}}, d: {q: {w: k: 333}}} }
let result = findAllPaths(myObject, "k")
// result = [a.k, c.b.k, d.q.w.k]
The result should be a list of all paths to the key anywhere in the nested object

You could create recursive function to do this using for...in loop.
let myObject = {a: {k:111, d:3}, b:"2", c: { b: {k: 222}}, d: {q: {w: {k: 333}}} }
function getAllPaths(obj, key, prev = '') {
const result = []
for (let k in obj) {
let path = prev + (prev ? '.' : '') + k;
if (k == key) {
result.push(path)
} else if (typeof obj[k] == 'object') {
result.push(...getAllPaths(obj[k], key, path))
}
}
return result
}
const result = getAllPaths(myObject, 'k');
console.log(result);

Related

How to recursively replace in object in Javascript?

I am trying to write a function that will recursively replace the string '{{obj.<key>}}' in an object obj with the value obj.<key>. I am assuming the object will always be named obj.
For example, if I have the following object:
let obj = { a: '123', b: '456', c: '{{obj.a}}' }
It would resolve to:
{ a: '123', b: '456', c: '123' }
I have tried writing the function deepReplace to recursive replace within an object and the function replace to perform the regex replacement in the string.
const deepReplace = (globalObj, obj) => {
switch (typeof(obj)) {
case 'string':
obj = replace(globalObj, obj)
break
case 'array':
obj.forEach(item => item = deepReplace(globalObj, item))
break
case 'object':
for (let key in obj) {
obj[key] = deepReplace(globalObj, obj[key])
}
break
default:
break
}
}
const _ = require('lodash')
const replace = (globalObj, item) => {
let newString = item
let matches = item.match(/[^{\}]+(?=})/g) || []
for (let match of matches) {
if (! match === /^obj\./) {
throw new Error(`Cannot replace ${match}`)
}
let target = match.replace(/^obj\./, '')
let value = _.get(globalObj, target)
if (value === undefined) {
throw new Error(`Cannot replace ${match}`)
}
newString = newString.replace(`{{${match}}}`, value)
}
return newString
}
I know replace works:
> replace(obj, 'value for a: {{obj.a}}, value for b: {{obj.b}}')
'value for a: 123, value for b: 456'
However, I can't get deepReplace to work. Why does the line obj = replace(globalObj, obj) in deepReplace not update the object? replace() returns correctly, but obj does not get updated.
> deepReplace(obj, obj)
Error: Cannot replace obj.a

Change nested objects to the same object whose values which are objects are the objects with parents as prototype

Experimenting with an idea. Given an object like:
T = {
a: 2,
b: 9,
c: {
a: 3,
d: 6,
e: {
f: 12
}
}
}
I want to mutate it such that every value that is an object, changes to the same object, with the parent object as prototype.
Meaning I'd like to be able to have the following outputs:
> T.c.b
9
> T.c.e.b
9
> T.c.e.a
3
> T.c.c.c
{a: 3, d: 6, e:[Object]}
I have already created the following functions that work almost as expected:
function chainer(object) {
for (const key in object) {
if (object[key] !== null && typeof (object[key]) === 'object') {
let Constructor = function () {
};
Constructor.prototype = object;
let objectValue = {...object[key]};
object[key] = new Constructor();
for (const savedKey in objectValue) {
object[key][savedKey] = objectValue[savedKey];
}
}
}
}
function chain(object) {
chainer(object);
for (const key in object) {
if (object[key] !== null && typeof (object[key]) === 'object') {
chainer(object[key]);
}
}
}
With the previous example it works as expected. Nevertheless, when I try with the following:
T = {a:4, g:{g:{g:{g:{g:{g:{g:{}}}}}}}}
The following output happens:
> T.a
4
> T.g.a
4
> T.g.g.a
4
> T.g.g.g.a
undefined
> T.g.g.g.g.a
undefined
I find it weird it only works up to a point, it makes me think perhaps its an issue with some limit I'm not aware.
Anyway, I'm getting dizzy and out of ideas, any thoughts?
This seems to work fine:
ouroboros = (x, parent = null) => {
if (!x || typeof x !== 'object')
return x;
let r = Object.create(parent);
Object.entries(x).forEach(([k, v]) => r[k] = ouroboros(v, r));
return r;
};
//
T = ouroboros({x: 4, a: {b: {c: {d: {e: {}}}}}});
console.log(T.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.x);
or, mutating objects, instead of copying:
ouroboros = (x, parent = null) => {
if (x && typeof x === 'object') {
Object.setPrototypeOf(x, parent);
Object.values(x).forEach(v => ouroboros(v, x));
}
};
If I am not mistaking you wanted to do something like this:
rec = function (o) {
return Object.keys(o).reduce((acc, key) => {
if (typeof acc[key] === "object") {
const kv = {...rec(acc[key]), ...o}
return {...acc, ...kv, get [key]() { return this}}
}
return acc;
},o)
}

Get json values for each level using javascript

I'm trying to use a recursive function to get the last key value form a simple json using javascript
I have this json:
{
'a': {
'b': {
'c': 12,
'd': 'Hello World'
},
'e': [1,2,3]
}
}
And my expected result is:
{
'a/b/c': 12,
'a/b/d': 'Hello World',
'a/e': [1,2,3]
}
I'm trying with:
function getDeepKeys(obj) {
var keys = [];
for (var key in obj) {
keys.push(key);
if (typeof obj[key] === "object") {
var subkeys = getDeepKeys(obj[key]);
keys = keys.concat(subkeys.map(function (subkey) {
return key + "/" + subkey;
}));
}
}
return keys;
}
But for some reason, it returns me:
a/b/c/d/e/0/1/, I'm not sure why it is adding those numbers there.
Someone has an idea about how I can do it?
You can do it iteratively with an explicit stack which has less overhead than recursion and won't blow the call stack:
const pathify = o => {
const paths = {};
const stack = [[o, []]];
while (stack.length) {
const [curr, path] = stack.pop();
for (const k in curr) {
if (typeof curr[k] === "object" && !Array.isArray(curr[k])) {
stack.push([curr[k], path.concat(k)]);
}
else {
paths[`${path.join("/")}/${k}`] = curr[k];
}
}
}
return paths;
};
console.log(pathify({'a':{'b':{'c':12,'d':'Hello World'},'e':[1,2,3]}}));
I think you may be making it more complicated than necessary. You can test for an array with Array.isArray and an non-object (typeof !== 'object) and just return the path and value. Otherwise recurse for each entry. reduce() is good for that. Passing the current path as an argument to the recursive function is convenient too:
let obj = {'a': {'b': {'c': 12,'d': 'Hello World'},'e': [1,2,3]}}
function getValues(obj, path = []){
return (Array.isArray(obj) || typeof obj !== 'object')
? {[path.join('/')]: obj}
: Object.entries(obj).reduce((acc, [key, val]) =>
Object.assign(acc, getValues(val, path.concat(key)) )
, {})
}
console.log(getValues(obj))
You can use the function Object.keys to loop the keys of the objects and with recursion, you can go deeper through the nested objects.
let obj = {'a': {'b': {'c': 12,'d': 'Hello World'},'e': [1,2,3]}},
result = Object.create(null),
loop = function (o, arr, result) {
Object.keys(o).forEach(k => {
arr.push(k);
if (typeof o[k] === 'object' && !Array.isArray(o[k])) loop(o[k], arr, result);
else result[arr.join('/')] = o[k];
arr.splice(-1);
});
};
loop(obj, [], result);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

js generate path string for nested property

I have an object with nested properties:
{
a: {
b: {
c: { min: 1, max: 2 },
d: 1
}
}
}
The nesting can be any amount of levels deep. It ends in either an object with one of the properties (min, max, in) or as a non object (string, number, bool)
I would like to generate a string for the path to those endpoints.
E.g. for above object I would like the following result:
objPaths(a)
=> {
"a.b.c": { min: 1, max: 2 }
"a.b.d": 1
}
You could create recursive function for this using for...in loop. Also if the value of the current property is an object you need to check if some of the keys is min or max before going to next level.
const obj = {"a":{"b":{"c":{"min":1,"max":2},"d":1}}}
const objPaths = (obj, paths = {}, prev = '') => {
for (let key in obj) {
let str = (prev ? prev + '.' : '') + key;
if (typeof obj[key] != 'object') paths[str] = obj[key];
else if (Object.keys(obj[key]).some(k => ['min', 'max'].includes(k))) paths[str] = obj[key];
else objPaths(obj[key], paths, str)
}
return paths
}
const result = objPaths(obj);
console.log(result)
You could take an iterative and recusive approach and check if the object contains min or max keys, then take the object or value otherwise itrate the object.
function getPath(object) {
function iter(o, p) {
if (o && typeof o === 'object' && !['min', 'max'].some(k => k in o)) {
Object.entries(o).forEach(([k, v]) => iter(v, p + (p && '.') + k));
return;
}
result[p] = o;
}
var result = {};
iter(object, '');
return result;
}
console.log(getPath({ a: { b: { c: { min: 1, max: 2 }, d: 1 } } }));

Get all namespaces within an object in Javascript?

I have a deeply nested object:
{ a: { b: { c: 3 }, d: 4 } }.
How to get all namespaces within this object?
So, I need to get:
['a.b.c', 'a.d'].
You can create recursive function using for...in loop.
var obj = {a: {b: {c: 3} }, d: 4 }
function getKeys(data, prev) {
var result = []
for (var i in data) {
var dot = prev.length ? '.' : '';
if (typeof data[i] == 'object') result.push(...getKeys(data[i], prev + dot + i))
else result.push(prev + dot + i)
}
return result;
}
console.log(getKeys(obj, ''))
Instead of for...in loop you can use Object.keys() and reduce().
var obj = {a: {b: {c: 3} }, d: 4 }
function getKeys(data, prev) {
return Object.keys(data).reduce(function(r, e) {
var dot = prev.length ? '.' : '';
if (typeof data[e] == 'object') r.push(...getKeys(data[e], prev + dot + e))
else r.push(prev + dot + e)
return r;
}, [])
}
console.log(getKeys(obj, ''))
var t = {a: {b: {c: 3} }, d: 4 };
path (t, '');
function path(t, sofar) {
if (Object.keys(t).length === 0)
console.log(sofar.substr(1));
var keys = Object.keys(t);
for (var i = 0 ; i < keys.length ; ++i) {
path(t[keys[i]], sofar+'.'+keys[i]);
}
}
You could create a script in order to flatten the object and return the keys. You could also think to convert it to an array and use the default flatten of arrays. Here an example of flattening the object.
var flattenObject = function(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
};
var obj = {a: {b: {c: 3} }, d: 4 }
console.log(Object.keys(flattenObject(obj))); // ['a.b.c', 'd']
p.s. your object in the question has a mistake, or what you want is not what you are asking. d is at the same level of a, so you can't achieve "a.d", but "d"
You could check the keys and iterate otherwise push the path to the result set.
function getKeys(object) {
function iter(o, p) {
var keys = Object.keys(o);
keys.length ?
keys.forEach(function (k) { iter(o[k], p.concat(k)); }):
result.push(p.join('.'));
}
var result = [];
iter(object, []);
return result;
}
var object = { a: { b: { c: 3 } }, d: 4 };
console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources