Get all namespaces within an object in Javascript? - 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; }

Related

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

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);

find possible paths in map : recursive function in javascript

doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
}
}
}
}
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
// console.log("Obj check" + JSON.stringify(nObj) + keystr)
arr.push(nObj);
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = (get(temp, concat.substr(0, concat.length - 1)));
if (obj != "") {
// console.log("existing arr "+JSON.stringify(arr))
obj[m] = (obj[0])[concat.substr(0, concat.length - 1)]
// console.log("hello "+JSON.stringify(obj) + " end hello")
arr.push(obj);
}
} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
return arr;
}
var result = (get(doc, 'a.*.e'))
console.log(result)
For input of 'a.*.e' the output should be {'a.d.e': {'f': 'blah blah blah'}}}. But I get all the replacement for wild card as well in the array. I am sure something is wrong but not able to detect it. Help would be appreciated.
You could change the structure of the operation a little bit with a recursive approach and an exit early exit often paradigm with checking of single parts with exit options, like
length, a part result is found,
falsy or not object types,
part at index is a star, then iterate all keys from the object, or
the part at index is a key, then call the function again.
At the end, with a found path, joint the path and generate a new property with the actual value of the object.
function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with * as wildcard for any level.
function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i);
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
console.log(get(doc, 'a.*.f'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First of all, since your desired output {'a.d.e': {'f': 'blah blah blah'}}} does not contain any array, but only plain objects, you should not need the variable arr in your code.
Instead, return nObj as function result, and declare it at the start, never clearing it.
Secondly, when you come back from the recursive call, the results need to be copied while prefixing the paths with what you already had. Note that checking for an empty array should not be done with != "", but anyway, you don't need that any more.
You could write this from scratch in different ways (see solution at end of answer), but I have first adapted your code to only change the bare minimum, with comments where I made the changes to make it work:
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
// *** Define here the object to return
var nObj = {};
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
// *** Move this to start of the function
//var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = get(temp, concat.substr(0, concat.length - 1));
// *** Return value is object with path(s) as keys
// *** Don't compare array with string
//if (arr != "") {
// *** Iterate over the returned object properties, and prefix them
for (var deepKey in obj) {
nObj[keystr + deepKey] = obj[deepKey];
}
//*** No need for array; we already have the object properties
//arr.push(obj);
//}
// *** No need for array
//} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
// *** Return object
return nObj;
}
var doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
}
var result = (get(doc, 'a.*.e'));
console.log(result);
Please also consider not name objects json when they are not: JSON is a text format, JavaScript object variables are not the same thing as JSON.
Compact ES6 solution
When you are used to array functions like reduce and a functional programming style, the following compact ES6 solution might appeal to you:
function get(obj, path) {
if (typeof path === 'string') path = path.split('.');
return !path.length ? { '': obj } // Match
: obj !== Object(obj) ? {} // No match
: (path[0] === '*' ? Object.keys(obj) : [path[0]]) // Candidates
.reduce( (acc, key) => {
const match = get(obj[key], path.slice(1)); // Recurse
return Object.assign(acc, ...Object.keys(match).map( dotKey =>
({ [key + (dotKey ? '.' + dotKey : '')]: match[dotKey] })
));
}, {});
}
const doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
};
const result = get(doc, 'a.*.e');
console.log(result);
List monad
Here's a solution which borrows ideas from the List monad to represent a computation which may have 0, 1, or more results. I'm not going to cover it in detail and I've only included enough of the List type to get a working solution. If you're interested in this sort of approach, you can do some more research on the topic or ask me a follow-up question.
I'm also using an auxiliary find function which is the recursive helper for get which operates the array of keys that get prepares
If you like this solution, I've written about the list monad in some other answers; you might find them helpful ^_^
const List = xs =>
({
value:
xs,
bind: f =>
List (xs.reduce ((acc, x) =>
acc.concat (f (x) .value), []))
})
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return List([{ [path.join('.')]: data }])
else if (key === '*')
return List (Object.keys (data)) .bind (k =>
find ([...path, k], keys, data[k]))
else if (data[key] === undefined)
return List ([])
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find ([], path.split ('.'), doc) .value
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
// { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]
Native arrays only
We don't have to do fancy List abstraction in order to achieve the same results. In this version of the code, I'll show you how to do it using nothing but native Arrays. The only disadvantage of this code is the '*'-key branch gets a little complicated by embedding the flatmap code inline with our function
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return [{ [path.join ('.')]: data }]
else if (key === '*')
return Object.keys (data) .reduce ((acc, k) =>
acc.concat (find ([...path, k], keys, data[k])), [])
else if (data[key] === undefined)
return []
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find([], path.split('.'), doc)
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
// { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]
Why I recommend the List monad
I do personally recommend the List monad approach as it keeps the body of the find function most clean. It also encompasses the concept of ambiguous computation and allows you to reuse that wherever you might require such a behaviour. Without using the List monad, you would rewrite the necessary code each time which adds a lot of cognitive load on the understanding of the code.
Adjust the shape of your result
The return type of your function is pretty weird. We're returning an Array of objects that only have one key/value pair. The key is the path we found the data on, and the value is the matched data.
In general, we shouldn't be using Object keys this way. How would we display the results of our match?
// get ('a.*', doc) returns
let result =
[ { 'a.b': { c: 'hello' } },
{ 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
result.forEach (match =>
Object.keys (match) .forEach (path =>
console.log ('path:', path, 'value:', match[path])))
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }
What if we returned [<key>, <value>] instead of {<key>: <value>}? It's much more comfortable to work with a result in this shape. Other reasons to support this is a better shape for your data is something like Array#entries or Map#entries()
// get ('a.*', doc) returns proposed
let result =
[ [ 'a.b', { c: 'hello' } ],
[ 'a.d', { c: 'sup', e: { f: 'blah blah blah' } } ] ]
for (let [path, value] of result)
console.log ('path:', path, 'value:', value)
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }
If you agree this is a better shape, updating the code is simple (changes in bold)
// List monad version
const find = (path, [key, ...keys], data) => {
if (key === undefined)
return List ([[path.join ('.'), data]])
...
}
// native arrays version
const find = (path, [key, ...keys], data) => {
if (key === undefined)
return [[path.join ('.'), data]]
...
}

JavaScript: Return property values only

I want to implement a function that returns an array of property values if the value is primitive (non-object or array) and property name starts with prefix.
For example
var values = function (obj, prefix) { ... }
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
As a result of values(testObj, 'a') function invocation I expect to get such array of primitives: [1, true].
Here is my try:
var values = function (obj, prefix) {
var res = [];
for (var i in obj) {
if (i.startsWith(prefix)) {
var v = obj[i];
if (typeof v === 'object') {
var r0 = arguments.callee(v, prefix);
res.push(r0);
} else {
res.push(v);
}
}
}
return res;
};
But it returns a wrong result: [1, []]. How can I fix it?
You could use a recursive approach for the values, you need.
function values(obj, prefix) {
var result = [];
Object.keys(obj).forEach(function (k) {
if (obj[k] !== null && typeof obj[k] === 'object') {
result = result.concat(values(obj[k], prefix));
} else if (k.startsWith(prefix)) {
result.push(obj[k]);
}
});
return result;
}
var testObj = { 'a': 1, 'ab': [{ 'c': 2, 'ac': true }] },
result = values(testObj, 'a');
console.log(result);
Following code works.
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
var values = function (obj, prefix) {
var res = [];
if(Array.isArray(obj)){
for(var j in obj){
res = res.concat(arguments.callee(obj[j], prefix));
}
}
else if(typeof obj == "object") {
for (var i in obj) {
if (i.startsWith(prefix)) {
var v = obj[i];
if (typeof v === 'object') {
res = res.concat(arguments.callee(v, prefix));
} else {
res.push(v);
}
}
}
}
return res;
};
console.log(values(testObj, 'a'));
This might be what you are looking for;
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
getValues = (o,x) => Object.keys(o)
.reduce((p,k) => p.concat(typeof o[k] === "object" ? getValues(o[k],x)
: k.indexOf(x) >= 0 ? o[k]
: [])
,[]);
console.log(getValues(testObj,"a"));
(My city energy is weak.)
Where the main problem is in the i.startsWith(prefix) condition. It avoids you to enter a object without property name including #prefix inside a array. For example:
{ a: 1, ab: [ /* 0: { 'c': 2, 'ac': true } */ ] }
As you see, the object in the array is ignored since its property name is 0, that's its index.
If you really want to get this result: [1, true] you'll have to skip the array and return the first item to res.push.
var values = function (obj, prefix) {
var res = [];
for (var i in obj) {
if (i.startsWith(prefix)) {
var v = obj[i];
if (typeof v === 'object') {
var isArray = v instanceof Array;
var r0 = arguments.callee(isArray ? v[0] : v, prefix);
res.push(isArray && r0.length === 1 ? r0[0] : r);
} else {
res.push(v);
}
}
}
return res;
};
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
var res = [];
var values = function (obj, prefix) {
for (var i in obj) {
var v = obj[i];
//Prefix check line can be moved here if you want to check the prefix for object
if (typeof v === 'object') {
arguments.callee(v, prefix);
} else {
if (i.startsWith(prefix)) { //Prefix Check
res.push(v);
}
}
}
return res;
};
console.log(values(testObj,'a'));
Please check this, this gives the output that you wanted.

Sorting nested array of objects with dynamic key

I am trying to create a sorting function that sorts a nested array of objects while giving a key dynamically (with different depths).
sortByKey(array, key){
var splitKey = key.split(".");
if(splitKey.length = 2){
return array.sort(function(a, b) {
var x = a[splitKey[0]][splitKey[1]]; var y = b[splitKey[0]][splitKey[1]];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
} else {
return array.sort(function(a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
}
I want to get rid of the if - else and use a for loop instead. The goal is that the function works with 'name', 'name.first' and 'name.first.another' (as an example). Is there a way to do this dynamically?
In other words, I want to use the same function with different arrays. So with one array I want to sort it calling sortByKey(array1, 'name') and with another sortByKey(array2, 'location.address') and maybe with a third sortByKey(array3, 'location.address.postalcode') or something like that.
Extract property extracting function
function prop(key) {
var keys = key.split('.');
return keys.reduce.bind(keys, function(obj, name) {
return obj[name]
})
}
and use it to well extract values :)
sortByKey(array, key){
var getKey = prop(key);
return array.sort(function(a, b){
var x = getKey(a); var y = getKey(b);
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
})
}
I think you mean something like this:
function sortByKey(array, key){
var splitKey = key.split(".");
return array.sort(function(a, b) {
var ta = a;
var tb = b;
for (var i=0; i<splitKey.length; i++) {
ta = ta[splitKey[i]];
};
/// return ((a < b) ? -1 : ((a > b) ? 1 : 0)); // Too complex ;-)
return a - b;
});
};
Your problem is a misused assignment, where it should be a comparison.
if (splitKey.length === 2) {
// ^^^
A shorter approach could use Array#reduce.
function sortByKey(array, key) {
var getValue = function (o, k) { return o[k]; },
keys = key.split(".");
return array.sort(function (a, b) {
return keys.reduce(getValue, a) - keys.reduce(getValue, b);
});
}
var array = [{ a: 5, b: { c: 2 } }, { a: 7, b: { c: 1 } }, { a: 1, b: { c: 3 } }];
sortByKey(array, 'a');
console.log(array);
sortByKey(array, 'b.c');
console.log(array);
ES6
function sortByKey(array, key) {
const getValue =
(keys => object => keys.reduce((o, k) => o[k], object))
(key.split('.'));
return array.sort((a, b) => getValue(a) - getValue(b));
}
var array = [{ a: 5, b: { c: 2 } }, { a: 7, b: { c: 1 } }, { a: 1, b: { c: 3 } }];
sortByKey(array, 'a');
console.log(array);
sortByKey(array, 'b.c');
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to $.extend 2 objects by adding numerical values together from keys with the same name?

I currently have 2 obj and using the jquery extend function, however it's overriding value from keys with the same name. How can I add the values together instead?
var obj1 = {
"orange": 2,
"apple": 1,
"grape": 1
};
var obj2 = {
"orange": 5,
"apple": 1,
"banana": 1
};
mergedObj = $.extend({}, obj1, obj2);
var printObj = typeof JSON != "undefined" ? JSON.stringify : function (obj) {
var arr = [];
$.each(obj, function (key, val) {
var next = key + ": ";
next += $.isPlainObject(val) ? printObj(val) : val;
arr.push(next);
});
return "{ " + arr.join(", ") + " }";
};
console.log('all together: ' + printObj(mergedObj));
And I get obj1 = {"orange":5,"apple":1, "grape":1, "banana":1}
What I need is obj1 = {"orange":7,"apple":2, "grape":1, "banana":1}
All $.extend does is join the two objects but it doesn't add the values, it overrides them.
You're going to have to do this manually. $.extend will be useful to add or modify fruits to your object but if you need the total sum you gonna have to loop:
var obj1 = { orange: 2, apple: 1, grape: 1 };
var obj2 = { orange: 5, apple: 1, banana: 1 };
var result = $.extend({}, obj1, obj2);
for (var o in result) {
result[o] = (obj1[o] || 0) + (obj2[o] || 0);
}
console.log(result); //=> { orange: 7, apple: 2, grape: 1, banana: 1 }
Demo: http://jsfiddle.net/elclanrs/emGyb/
Working Demo
That's not how .extend() works; you'll have to implement your own:
function mergeObjects() {
var mergedObj = arguments[0] || {};
for (var i = 1; i < arguments.length; i++) {
var obj = arguments[i];
for (var key in obj) {
if( obj.hasOwnProperty(key) ) {
if( mergedObj[key] ) {
mergedObj[key] += obj[key];
}
else {
mergedObj[key] = obj[key];
}
}
}
}
return mergedObj;
}
Usage:
var obj1 = { "orange": 2, "apple": 1, "grape": 1 };
var obj2 = { "orange": 5, "apple": 1, "banana": 1 };
var mergedObj = mergeObjects( obj1, obj2);
// mergedObj {"orange":7,"apple":2,"grape":1,"banana":1}
Of course, like .extend(), this will work with any number of objects -- not just 2.
No fancy stuff, just simple Javascript:
The c[k] || 0 is there in the event that c[k] has no value, it gets to be zero.
var a = {orange:2, apple:1, grape:1};
var b = {orange:5, apple:1, banana:1};
var c = {};
var k;
for (k in a) {c[k] = 0 + a[k] + (c[k] || 0)}
for (k in b) {c[k] = 0 + b[k] + (c[k] || 0)}
window.alert(JSON.stringify(c));
Here's some code you could use:
obj1 = {"orange":2,"apple":1, "grape":1};
obj2 = {"orange":5,"apple":1, "banana":1};
var joined = {};
// add numbers in arr to joined
function addItems(arr) {
// cycle through the keys in the array
for (var x in arr) {
// get the existing value or create it as zero, then add the new value
joined[x] = (joined[x] || 0) + arr[x];
}
}
addItems(obj1);
addItems(obj2);
console.log(JSON.stringify(joined));
Output:
{"orange":7,"apple":2,"grape":1,"banana":1}

Categories

Resources