Prune JSON to given target and get all parent keys - javascript

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

Related

Separating (n) keys from array of objects into a single array with keys names

I need to perform filter in the array of objects to get all the keys. Although, whenever there is a obj inside of that key, I would need to get the key name and concat with the key name from the obj, so for example:
const data = [ id: 5, name: "Something", obj: { lower: True, higher: False } ]
result = ["id", "name", "obj.lower", "obj.higher"]
I could manage to do the above code, but, if there is more objs inside the data, I would need to keep adding a if condition inside of my logic, I would like to know if there is any other way, so it doesn't matter how many objects I have inside the objects, It will concat always.
The code I used from the above mention:
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const csvData = [
Object.keys(itemsArray[0]),
...itemsArray.map(item => Object.values(item))
].map(e => e.join(",")).join("\n")
// Separating keys
let keys = []
const allKeys = Object.entries(itemsArray[0]);
for (const data of allKeys) {
if (typeof data[1] === "object") {
const gettingObjKeys = Object.keys(data[1]);
const concatingKeys = gettingObjKeys.map((key) => data[0] + "." + key);
keys.push(concatingKeys);
} else {
keys.push(data[0])
}
}
//Flating
const flattingKeys = keys.reduce((acc, val: any) => acc.concat(val), []);
What I would like to achieve, lets suppose I have this array of object:
const data =
[
{ id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}}
...
]
Final result = ["id", "obj.name", "obj.obj2.name2", "obj.obj2.test"]
OBS: The first obj contains all the keys I need, no need to loop through other to get KEYS.
I would like to achieve, all the keys from the first object of the array, and if there is objects inside of objects, I would like to concat the obj names (obj.obj2key1)
You could map the key or the keys of the nested objects.
const
getKeys = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getKeys(v).map(s => `${k}.${s}`)
: k
),
getValues = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getValues(v)
: v
),
data = { id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
keys = getKeys(data),
values = getValues(data);
console.log(keys);
console.log(values);
.as-console-wrapper { max-height: 100% !important; top: 0; }
something like this
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const item = itemsArray[0];
const getAllKeys = (obj, prefix=[]) => {
if(typeof obj !== 'object'){
return prefix.join('.')
}
return Object.entries(obj).flatMap(([k, v]) => getAllKeys(v, [...prefix, k]))
}
console.log(getAllKeys(item))
The OP solution can be simplified by accepting a prefix param (the parent key) and a results param (defaulted to [] and passed into the recursion) to do the flattening...
let obj = { key0: 'v0', key1: { innerKey0: 'innerV0', innerInner: { deeplyNested: 'v' } }, key2: { anotherInnerKey: 'innerV' } }
function recursiveKeys(prefix, obj, result=[]) {
let keys = Object.keys(obj);
keys.forEach(key => {
if (typeof obj[key] === 'object')
recursiveKeys(key, obj[key], result);
else
result.push(`${prefix}.${key}`)
});
return result;
}
console.log(recursiveKeys('', obj))
function getKeys(obj) {
return Object.keys((typeof obj === 'object' && obj) || {}).reduce((acc, key) => {
if (obj[key] && typeof obj[key] === 'object') {
const keys = getKeys(obj[key]);
keys.forEach((k) => acc.add(`${key}.${k}`));
} else {
acc.add(key);
}
return acc;
}, new Set());
}
// accumulate the keys in a set (the items of the array may
// have different shapes). All of the possible keys will be
// stored in a set
const s = itemsArray.reduce(
(acc, item) => new Set([...acc, ...getKeys(item)]),
new Set()
);
console.log('Keys => ', Array.from(s));
You can use recursion as follows. Since typeof([1,3,5]) is object, we also have to confirm that value is not an array, !Array.isArray(value):
const obj = { id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}};
const getKeys = (o,p) => Object.entries(o).flatMap(([key,value]) =>
typeof(value) === 'object' && !Array.isArray(value) ?
getKeys(value, (p?`${p}.`:"") + key) :
(p ? `${p}.`: "") + key
);
console.log( getKeys(obj) );

Sort only few objects from an array of object coming from api response in React

I am looking for an efficient way of sorting the API response which is array of Objects. This Array has many fields and I just want to sort only few of them.
The Array looks like this
result = {type: Array(), status: Array(), nature: Array(), health: Array(), fitness: Array(), wealth: Array()}
and Array have name and value property like {name:"", value:""}
so let's say I just need to sort type, status, and nature out of this result. The thing that I have tried now looks like this which juts sorts one of the records.
const typeName = "type"
if(result[typeName]){
result[typeName] = sortingFunction(result[typeName], "name")
}
Now I need to sort other fields as well and also for few fields I need to sort on the basis of "value" property as well.
So please let me know if you have any efficient way of doing this.
You could create a sort function which can sort the given input object for the given keys.
I have create a sample function for sorting.
This function has two parameters.
First the object which needs to be sorted
Second option, you can pass the option for sort.
a. sortBy: Name of the property on which the function will perform the sort .
b. sortKeys: Array | String, the keys/key of the object which need to be sorted.
Function:
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
Example:
//sortObject(sampleObject, { sortby: ["name", "value"], sortKeys: ["status", "type"] });
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
let sampleObject = {
type: [
{ name: "c", value: 4 },
{ name: "a", value: 2 },
{ name: "b", value: 1 },
{ name: "d", value: 3 },
],
status: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
nature: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
}
sortObject(sampleObject, { sortby: "value", sortKeys: ["type"] });
sortObject(sampleObject, { sortby: "name", sortKeys: ["status", "nature"] });
console.log(sampleObject)
One way is to translate the object of arrays into an array of objects, then merge it back after sorting.
const result = {
type: ['foo', 'bar', 'baz'],
status: [4, 3, 5],
nature: ['forest', 'animal', 'water'],
health: ['athlete', 'couch potato', 'dead'],
fitness: [200, 50, 60],
wealth: [5, 2, 99]
};
// 1. combine
const combined = result.type.map((_, index) => {
return Object.fromEntries(Object.keys(result).map(key => [key, result[key][index]]));
});
// 2. example sort by status
combined.sort((a, b) => a.status - b.status)
// 3. merge
combined.forEach((object, index) => {
for (const [key, value] of Object.entries(object)) {
result[key][index] = value
}
})
console.log(result);

Updating the object based on the presence of the key else form a new object : Javascript

Have an object with following format:
let obj = {
p2: {
p21: [
{
key1: "val1",
value1: "val2"
},
{
prop: "test",
value: "dummy"
}
]
}
}
I basically have to see if there is an object that has key "prop" and replace with whatever the passed value. If such key does not present create an object with { "prop" : "test" , value: passed_value} and add it to the p21 array. Also this object containing key "prop" can be present anywhere inside p21 array
Should be something like this
function checkAndUpdate(replacingValue) {
if(obj's p21 has key name "prop")
{
//update its "value" to the passed "replacingValue"
}
else //if "prop" key not present
{
// create an object with { "prop": "test" , value: replacingValue} and add it to p21 array
}
}
Have tried following:
obj.p2.p21.map((elem) => {
if(Object.keys(elem)[0] === "prop")
elem.value = updateValue;
})
Use .findIndex to find the index of the matching object. If it exists, .slice the array before and after it, and in the middle, insert the updated object. Otherwise, just spread the object into the array:
let obj = {
"p2": {
"p21": [{
"key1": "val1",
"value1": "val2",
},
{
"prop": "test",
"value": "dummy"
}
]
}
}
function checkAndUpdate(value) {
const newObj = { prop: 'test', value}
const arr = obj.p2.p21;
const index = arr.findIndex(obj => obj.hasOwnProperty('prop'));
const newArr = index !== -1
? [...arr.slice(0, index), newObj, ...arr.slice(index + 1)]
: [...arr, newObj];
const fullNewObj = {
...obj,
p2: {
...obj.p2,
p21: newArr
}
};
return fullNewObj;
}
console.log(checkAndUpdate('foo'));
To also check whether the value is undefined, and remove the matching object from the array if so, just make the appropriate if checks, and slice the array as needed:
let obj = {
"p2": {
"p21": [{
"key1": "val1",
"value1": "val2",
},
{
"prop": "test",
"value": "dummy"
}
]
}
}
function checkAndUpdate(value) {
const newObj = { prop: 'test', value}
const arr = obj.p2.p21;
const index = arr.findIndex(obj => obj.hasOwnProperty('prop'));
const newArr = (() => {
if (index === -1) {
if (value === undefined) {
return arr;
}
return [...arr, newObj];
}
if (value === undefined) {
return [...arr.slice(0, index), ...arr.slice(index + 1)];
}
return [...arr.slice(0, index), newObj, ...arr.slice(index + 1)];
})();
const fullNewObj = {
...obj,
p2: {
...obj.p2,
p21: newArr
}
};
return fullNewObj;
}
console.log(checkAndUpdate('foo'));
You can use Array.find to find the object that hasOwnProperty prop and update
let obj = {
"p2": {
"p21": [{
"key1": "val1",
"value1": "val2",
},
{
"prop": "test",
"value": "dummy"
}
]
}
}
function rep(value) {
if (!value.length) {
obj.p2.p21 = [...obj.p2.p21].filter(e => e.hasOwnProperty('prop'));
return;
}
let ele = obj.p2.p21.find(e => e.hasOwnProperty('prop'));
if (ele) {
ele.value = value
return;
}
obj.p2.p21.push({
"prop": "test",
value
})
}
rep('test');
console.log(JSON.stringify(obj))
rep('');
console.log(JSON.stringify(obj))

Javascript reflection: Get nested objects path

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

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

Categories

Resources