Javascript reflection: Get nested objects path - javascript

In this stackoverflow thread, i learnt you can get a object path via a simple string.
Accessing nested JavaScript objects with string key
consider the following:
var person = { name: "somename", personal: { weight: "150", color: "dark" }};
var personWeight = deep_value(person,"personal.weight");
I an trying to construct an array of the object values who are not of type 'object' from my 'person' object.
Hence the array would look like:
[['name', []],['personal.weight', []],['personal.color', []]];
I want them to look in that form because i have further use for it down the road.
That's what I've tried:
var toIterate = { name: "somename", personal: { age: "19", color: "dark" } }
var myArray = [];
$.each(toIterate, recursive);
function recursive(key, value) {
if (key !== null) {
myArray.push([key, []]);
}
else {
$.each(value, recursive);
}
}
console.log(myArray);

Just use recursion to walk the object.
var person = {
name: "somename",
personal: {
weight: "150",
color: "dark",
foo: {
bar: 'bar',
baz: 'baz'
},
empty: {
}
}
};
// however you want to do this
var isobject = function(x){
return Object.prototype.toString.call(x) === '[object Object]';
};
var getkeys = function(obj, prefix){
var keys = Object.keys(obj);
prefix = prefix ? prefix + '.' : '';
return keys.reduce(function(result, key){
if(isobject(obj[key])){
result = result.concat(getkeys(obj[key], prefix + key));
}else{
result.push(prefix + key);
}
return result;
}, []);
};
var keys = getkeys(person);
document.body.innerHTML = '<pre>' + JSON.stringify(keys) + '</pre>';
Then use Array.prototype.map to massage the array of keys into your preferred format.
Note the behaviour with person.personal.empty.
This does seem like a strange way to store an object's keys. I wonder what your 'further use for it down the road' is.

This is what worked for me. Note that, a raw map is created first and then mapped to an join the items in the Array with ..
var toIterate = {
name: "somename",
personal: {
age: "19",
color: "dark"
}
};
console.log(getObjPath(toIterate).map(item => item.join('.')));
function isObject(x) {
return Object.prototype.toString.call(x) === '[object Object]';
};
function getObjPath(obj, pathArray, busArray) {
pathArray = pathArray ? pathArray : [];
if (isObject(obj)) {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (isObject(obj[key])) {
busArray = busArray ? bussArray : [];
busArray.push(key);
getObjPath(obj[key], pathArray, busArray);
} else {
if (busArray) {
pathArray.push(busArray.concat([key]));
} else {
pathArray.push([key]);
}
}
}
}
}
return pathArray;
}
Good Luck...

I found the following solution on github.
https://github.com/mariocasciaro/object-path

Related

Prune JSON to given target and get all parent keys

I want to access all parents of a given target key
for example I want to give macbook_pro and get somthing like this :
['electronics', 'computers', 'mac']
let testData = {
cars : {
sedan : {
toyota : 'value1',
kia : 'value2',
mercedes : 'value3'
},
compact : {
bugatti : 'value1',
bugatti : 'value1',
}
},
electronics : {
computers : {
mac : {
macbook_pro : "value 1 1",
macbook_air : "value 1 1"
},
pcs : 'value2'
},
mobiles : {
apple : 'value',
samsung : 'value'
}
}
};
I tried to write a recursive function to get all keys. It works but it return all keys of other nodes too.
let keys = [];
function collectKeys(obj, breakKey){
for (let key in obj){
if (typeof obj[key] === 'object' && obj[key].constructor === Object){
keys.push(key);
collectKeys(obj[key], breakKey);
}
if (key === breakKey){
break;
}
}
}
You could use this recursive function:
function getPath(obj, key) {
if (Object(obj) !== obj) return; // It's a primitive value: No match
if (key in obj) return []; // found it!
for (let prop in obj) {
const path = getPath(obj[prop], key);
if (path) return [prop, ...path];
}
}
const testData = {cars: {sedan: {toyota: 'value1',kia: 'value2',mercedes: 'value3'},compact: {bugatti: 'value1'}},electronics: {computers: {mac: {macbook_pro: 'value 1 1',macbook_air: 'value 1 1'},pcs: 'value2'},mobiles: {apple: 'value',samsung: 'value'}}};
console.log(getPath(testData, "pcs"));
The problem in your code is that you do keys.push(key); at a time when it is not sure that the current path will lead to match. If the recursive call doesn't find a match then that key should be popped again from keys.
As your function does not return anything, you really don't know whether the recursive call found a match or not, yet that is something you need.
I found a path using recursive tree traversal algorithm.
const testData = {
cars: {
sedan: {
toyota: 'value1',
kia: 'value2',
mersedes: 'value3'
},
compact: {
bugatti: 'value4'
}
},
electronics: {
computers: {
mac: {
macbook_pro: 'value5',
macbook_air: 'value6'
},
pcs: 'value7'
},
mobiles: {
apple: 'value8',
samsung: 'value9'
}
}
};
function getPath(dataObject, value) {
let foundPath;
function collectKeys(data, path = []) {
Object.keys(data).forEach(key => {
if (key === value) {
foundPath = path;
return;
}
if (typeof data[key] !== 'string') {
collectKeys(data[key], path.concat([key]));
}
});
}
collectKeys(dataObject);
return foundPath;
}
console.log((getPath(testData, 'sedan')).join(',') === ['cars'].join(','));
console.log((getPath(testData, 'macbook_pro')).join(',') === ['electronics', 'computers', 'mac'].join(','));
Prune JSON to given target and get all parent keys
I found this problem very interesting and tried to solve (in a modular way) using recursion as follows.
Here only getParentKeys() will return you the expected result as you want.
Other functions are just helpers(callees) or independent.
Just go through the code starting from function testcase() which is starter.
function getAllMatchedKeys(testData, searchWord) {
let type = Object.prototype.toString.call(testData).slice(8, -1) // Array, Object, String, Number
let arr = [];
if(type == 'Object') { // If it is object
let keys = Object.keys(testData);
for(let key of keys) {
if(key === searchWord) {
arr.push(key);
return arr;
}
if(Object.prototype.toString.call(testData[key]).slice(8, -1) === 'Object') {
arr = getAllMatchedKeys(testData[key], searchWord);
if(arr.length !== 0) {
arr.push(key)
return arr;
}
}
}
}
return arr;
}
function getKeys(testData, searchWord) {
let allKeys = getAllMatchedKeys(testData, searchWord);
let parentKeys = allKeys.slice(1).reverse();
let keys = {
allKeys: allKeys,
parentKeys: parentKeys
};
return keys;
}
function getParentKeys(testData, searchWord) {
/*
Returns the parent keys, excluing the search word
*/
let keys = getKeys(testData, searchWord);
return keys["parentKeys"];
}
function testcase() {
/*
Test cases
*/
let testData = {
cars: {
sedan: {
toyota: 'value1',
kia: 'value2',
mercedes: 'value3'
},
compact: {
bugatti: 'value1'
},
toyota: {
car1: 'car-1',
car2: 'car-2',
car3: {
redbull: 'favourite'
}
}
},
electronics: {
computers: {
mac: {
macbook_pro: "value 1 1",
macbook_air: "value 1 2"
},
pcs: 'value2'
},
mobiles: {
apple: "value",
samsung: "value"
}
}
};
// TEST CASE 1
let macbookAllKeys = getKeys(testData, 'macbook_pro'); // Object
/*
{ allKeys: [ 'macbook_pro', 'mac', 'computers', 'electronics' ],
parentKeys: [ 'electronics', 'computers', 'mac' ] }
*/
// Pretty printing
console.log(JSON.stringify(macbookAllKeys, null, 4));
/*
{
"allKeys": [
"macbook_pro",
"mac",
"computers",
"electronics"
],
"parentKeys": [
"electronics",
"computers",
"mac"
]
}
*/
let macbookParentKeys = getParentKeys(testData, 'macbook_pro');
console.log(macbookParentKeys); /* [ 'electronics', 'computers', 'mac' ] */
// TEST CASE 2
let kiaParentKeys = getParentKeys(testData, 'kia');
console.log(kiaParentKeys); /* [ 'cars', 'sedan' ] */
// TEST CASE 3 (I added extra keys to your testData for this)
let redbullParentKeys = getParentKeys(testData, 'redbull');
console.log(redbullParentKeys); /* [ 'cars', 'toyota', 'car3' ] */
// TEST CASE 4
let sedanParentKeys = getParentKeys(testData, 'sedan');
console.log(sedanParentKeys); /* [ 'cars' ] */
}
// Start
testcase();

Construct new object in JS using [ ] notation

I'm trying to build a new object from an old object recursively like:
var arr = {};
var markCheckBoxes = function(obj){
var trackArr = new Array();
for(var prop in obj){
if(!!obj[i] && typeof(obj[i])=="object"){
trackArr.push(i);
markCheckBoxes(obj[i]);
trackArr.pop();
}
else{
var str = trackArr.join(".") + "." + i;
arr[str] = true;
}
}
};
But this creates a flat object. For example if obj is:
obj = {
prop1: {
subprop1: "text",
subprop2: "extra"
}, prop2:{
subprop: {another: 3}
}
}
Then the above function creates an object with keys:
{
"prop1.subprop1": true,
"prop1.subprop2": true,
"prop2.subprop.another": true
}
Assuming OP want to make other copy of object with true value for the keys, you can modify the code as below
var makeChecks = function(obj){
var cloneTo = {};
for(var prop in obj){
if(obj.hasOwnProperty(prop) && typeof(obj[prop]) === 'object'){
cloneTo[prop] = makeChecks(obj[prop]);
}
else{
cloneTo[prop] = true;
}
}
return cloneTo;
}
var obj = {
prop1: {
subprop1: "text",
subprop2: "extra"
}, prop2:{
subprop: {another: 3}
}
}
var output = makeChecks(obj);
console.log(output)
/*
{
"prop1": {
"subprop1": true,
"subprop2": true
},
"prop2": {
"subprop": {
"another": true
}
}
}
*/

How to remove a property from nested javascript objects any level deep?

Let's say I have nested objects, like:
var obj = {
"items":[
{
"name":"Item 1",
"value": "500",
"options": [{...},{...}]
},
{
"name":"Item 2",
"value": "300",
"options": [{...},{...}]
}
],
"name": "Category",
"options": [{...},{...}]
};
I want to remove the options property from any level deep from all the objects. Objects can be nested within objects, and arrays as well.
We're currently using Lodash in the project, but I'm curious about any solutions.
There is no straight forward way to achieve this, however you can use this below function to remove a key from JSON.
function filterObject(obj, key) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
filterObject(obj[i], key);
} else if (i == key) {
delete obj[key];
}
}
return obj;
}
and use it like
var newObject = filterObject(old_json, "option");
Modifying the above solution, To delete "dataID" which appears multiple times in my JSON . mentioned below code works fine.
var candidate = {
"__dataID__": "Y2FuZGlkYXRlOjkuOTI3NDE5MDExMDU0Mjc2",
"identity": {
"__dataID__": "aWRlbnRpdHk6NjRmcDR2cnhneGE3NGNoZA==",
"name": "Sumanth Suvarnas"
},
};
candidate = removeProp(candidate, "__dataID__")
console.log(JSON.stringify(candidate, undefined, 2));
function removeProp(obj, propToDelete) {
for (var property in obj) {
if (typeof obj[property] == "object") {
delete obj.property
let newJsonData= this.removeProp(obj[property], propToDelete);
obj[property]= newJsonData
} else {
if (property === propToDelete) {
delete obj[property];
}
}
}
return obj
}
A little modification of void's answer that allows for deletion of propertise which are also objects
function filterObject(obj, key) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (i == key) {
delete obj[key];
} else if (typeof obj[i] == 'object') {
filterObject(obj[i], key);
}
}
return obj;
}
We now use object-scan for data processing tasks like this. It's very powerful once you wrap your head around it. Here is how you'd answer your questions
// const objectScan = require('object-scan');
const prune = (input) => objectScan(['**.options'], {
rtn: 'count',
filterFn: ({ parent, property }) => {
delete parent[property];
}
})(input);
const obj = { items: [{ name: 'Item 1', value: '500', options: [{}, {}] }, { name: 'Item 2', value: '300', options: [{}, {}] }], name: 'Category', options: [{}, {}] };
console.log(prune(obj));
// => 3
console.log(obj);
// => { items: [ { name: 'Item 1', value: '500' }, { name: 'Item 2', value: '300' } ], name: 'Category' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I had similar issues. So, I developed the following library. Please see the source code of the library on GitHub, and you can download it using npm.
You can use the function removePropertiesDeeply together with isInitialized as below to remove all non-initialized properties (such as an empty object, empty array, empty string, or non-finite number).
const {removePropertiesDeeply, isInitialized} = require("#thedolphinos/utility4js");
const object = {
a: null,
b: "",
x: {
a: null,
b: ""
},
y: [],
z: [
null,
"",
{a: null, b: ""},
[[{a: {b: null, c: ""}}]],
"abc"
]
};
removePropertiesDeeply(object, (x) => !isInitialized(x));
console.log(JSON.stringify(object)); // See that the object becomes {"z":["abc"]}.
I had a similar issue and I got it resolved. I hope my solution might be helpful to someone.
I use Es6 ... spread operator to do a shallow copy of an object and made null to property I was not interested.
const newObject = {
...obj.items,
...obj.name,
options: null // option property will be null.
}
function omit(source) {
return isArray(source)
? source.map(omit)
: isObject(source)
? (({ options, ...rst }) => mapValues(rst, omit))(source)
: source;
}
as with lodash, that's an easy thing, also you can specify the key via an param like this
function omit(source, omitKey) {
return isArray(source)
? source.map(partialRight(omit,omitKey)))
: isObject(source)
? (({[omitKey]: _, ...rst }) => mapValues(rst, partialRight(omit,omitKey)))(source)
: source;
}
You can remove properties given a condition using following function:
// Warning: this function mutates original object
const removeProperties = (obj, condition = (key, value) => false) => {
for (var key in obj) {
const value = obj[key]
if (!obj.hasOwnProperty(key)) continue
if (typeof obj[key] === "object") {
removeProperties(obj[key], condition)
} else if (condition(key, value)) {
delete obj[key]
}
}
return obj
}
Examples:
// Remove all properties where key is equal to 'options'
removeProperties(someObject, (key, value) => key === 'options'))
// Remove all properties where key starts with 'ignore_'
removeProperties(someObject, (key, value) => key.startsWith('ignore_'))
// Remove all properties where value is null
removeProperties(someObject, (key, value) => value === null))

How to get all key in JSON object (javascript)

{"document":
{"people":[
{"name":["Harry Potter"],"age":["18"],"gender":["Male"]},
{"name":["hermione granger"],"age":["18"],"gender":["Female"]},
]}
}
From this JSON example, I would like to get the keys such as name, age, gender for each people.
How to do this?
I use Object.keys which is built into JavaScript Object, it will return an array of keys from given object MDN Reference
var obj = {name: "Jeeva", age: "22", gender: "Male"}
console.log(Object.keys(obj))
Try this
var s = {name: "raul", age: "22", gender: "Male"}
var keys = [];
for(var k in s) keys.push(k);
Here keys array will return your keys ["name", "age", "gender"]
var input = {"document":
{"people":[
{"name":["Harry Potter"],"age":["18"],"gender":["Male"]},
{"name":["hermione granger"],"age":["18"],"gender":["Female"]},
]}
}
var keys = [];
for(var i = 0;i<input.document.people.length;i++)
{
Object.keys(input.document.people[i]).forEach(function(key){
if(keys.indexOf(key) == -1)
{
keys.push(key);
}
});
}
console.log(keys);
ES6 of the day here;
const json_getAllKeys = data => (
data.reduce((keys, obj) => (
keys.concat(Object.keys(obj).filter(key => (
keys.indexOf(key) === -1))
)
), [])
)
And yes it can be written in very long one line;
const json_getAllKeys = data => data.reduce((keys, obj) => keys.concat(Object.keys(obj).filter(key => keys.indexOf(key) === -1)), [])
EDIT: Returns all first order keys if the input is of type array of objects
var jsonData = { Name: "Ricardo Vasquez", age: "46", Email: "Rickysoft#gmail.com" };
for (x in jsonData) {
console.log(x +" => "+ jsonData[x]);
alert(x +" => "+ jsonData[x]);
}
This function should return an array of ALL the keys (i.e. the key names) in a JSON object including nested key/value pairs.
function get_all_json_keys(json_object, ret_array = []) {
for (json_key in json_object) {
if (typeof(json_object[json_key]) === 'object' && !Array.isArray(json_object[json_key])) {
ret_array.push(json_key);
get_all_json_keys(json_object[json_key], ret_array);
} else if (Array.isArray(json_object[json_key])) {
ret_array.push(json_key);
first_element = json_object[json_key][0];
if (typeof(first_element) === 'object') {
get_all_json_keys(first_element, ret_array);
}
} else {
ret_array.push(json_key);
}
}
return ret_array
}
Using this function on the OP's original object
const op_object =
{
"document":{
"people":[
{
"name":[
"Harry Potter"
],
"age":[
"18"
],
"gender":[
"Male"
]
},
{
"name":[
"hermione granger"
],
"age":[
"18"
],
"gender":[
"Female"
]
}
]
}
}
var all_keys = [];
function get_all_json_keys(json_object, ret_array = []) {
for (json_key in json_object) {
if (typeof(json_object[json_key]) === 'object' && !Array.isArray(json_object[json_key])) {
ret_array.push(json_key);
get_all_json_keys(json_object[json_key], ret_array);
} else if (Array.isArray(json_object[json_key])) {
ret_array.push(json_key);
first_element = json_object[json_key][0];
if (typeof(first_element) === 'object') {
get_all_json_keys(first_element, ret_array);
}
} else {
ret_array.push(json_key);
}
}
return ret_array
}
get_all_json_keys(op_object, all_keys);
console.log(all_keys);
should yield
[ 'document', 'people', 'name', 'age', 'gender' ]
Note: This will return a unique list of all key names.
We must "parse" our jsonObject
console.log('{"key0":"value0", "key1":"value1"}');
var jsonObject = JSON.parse('{"key0":"value0", "key1":"value1"}')
Object.keys(jsonObject).forEach(key => {
console.log(jsonObject[key]); //values
console.log(key); //keys
})

Iterate through JSON and change arrays to strings

I have a JSON object that was returned from an XML to js function. This xml converter creates arrays for every entry even when they should be strings. I cannot modify this original function so therefore I would like to take my final json object, iterate through it, detect if a value is an array of length 1 and, if so, change that array to a string.
Original object:
var json = {
user: {
name: ["bob"],
email: ["bob#example.org"]
},
items: [{
name: ["Object 1"]
},{
name: ["Object 2"]
}]
}
Should become:
var json = {
user: {
name: "bob",
email: "bob#example.org"
},
items: [{
name: "Object 1"
},{
name: "Object 2"
}]
}
I have considered the reviver function but a) I would like to avoid going back to a string and b) I am not sure if that would even work as it will probably just feed me each array element individually.
This recursive function seems to work for this problem:
function simplify(obj) {
for (var k in obj) {
if (Object.prototype.toString.call(obj[k]) == '[object Array]' && obj[k].length == 1) {
obj[k] = obj[k][0];
}
else if (typeof obj[k] == 'object') {
obj[k] = simplify(obj[k]);
}
}
return obj;
}
simplify(json);
Demo: http://jsfiddle.net/xkz4W/
Here's a recursive way to do it:
function flattenArrays(data) {
function processItem(item) {
if (Array.isArray(item)) {
if (item.length === 1 && typeof item[0] === "string") {
data[prop] = item[0];
} else {
for (var i = 0; i < item.length; i++) {
processItem(item[i]);
}
}
} else if (typeof item === "object") {
flattenArrays(item);
}
}
for (var prop in data) {
processItem(data[prop]);
}
}
Working demo: http://jsfiddle.net/jfriend00/L5WKs/

Categories

Resources