Extract keys from object and nested array - javascript

I have an array of objects, those objects also have in some cases nested arrays, which contain objects.
Each object as a property key which I need to extract.
An example of the JSON I am dealing with is...
{
"items": [
{
"key":"main",
"foo":"bar",
"children":[
{
"key":"one",
"foo":"barboo"
},
{
"key":"two",
"foo":"boobaz"
}
]
},
{
"key":"secondary",
"foo":"baz",
"children":[
{
"key":"one",
"foo":"barboobaz"
},
{
"key":"two",
"foo":"boobazfoo"
}
]
}
]
}
Currently I am mapping over items and returning key, then where I find children I am mapping again returning key.
Something like this pseudo code...
class SomeClass {
let contentKeys = []
extractKeys = navItems => {
navItems.map(item => this.appendToContentRequest(item));
return this.contentKeys.join(',');
};
appendToContentRequest(item) {
if (~!this.contentKeys.indexOf(item.key)) {
this.contentKeys.push(item.key);
}
if (item.children) {
item.children.map(child => this.appendToContentRequest(child));
}
}
}
I don't like this though, it feels very 'hacky' and not very functional.
Is there a better way to achieve this?

You can use below recursive function to take any key's value of any nested array of objects
function extract(array, keyName) {
return array.reduce((a, b) =>
a.concat(...Object.keys(b).map(e => e === keyName ? b[e] : (Array.isArray(b[e]) ? extract(b[e], keyName) : null))), []).filter(e => e);
}
console.log(extract(obj.items, 'key'));
<script>
const obj = {
"items": [{
"key": "main",
"foo": "bar",
"children": [{
"key": "one",
"foo": "barboo"
},
{
"key": "two",
"foo": "boobaz"
}
]
},
{
"key": "secondary",
"foo": "baz",
"children": [{
"key": "one",
"foo": "barboobaz"
},
{
"key": "two",
"foo": "boobazfoo"
}
]
}
]
}
</script>

var data = {
"items": [
{
"key":"main",
"foo":"bar",
"children":[
{
"key":"one",
"foo":"barboo"
},
{
"key":"two",
"foo":"boobaz"
}
]
},
{
"key":"secondary",
"foo":"baz",
"children":[
{
"key":"one",
"foo":"barboobaz"
},
{
"key":"two",
"foo":"boobazfoo"
}
]
}
]
}
function flatNestedKey(array) {
var result = [];
const context = this;
array.forEach(function (a) {
result.push(a.key);
if (Array.isArray(a.children)) {
result = result.concat(context.flatNestedKey(a.children));
}
});
return result;
}
var keys = flatNestedKey(data.items);
console.log(keys);

Related

How in JS to merge in one object two json objects where the ID of on object correspond on the same ID of the second object

My question relates to the fact I'm querying 2 different objects from DB and the result is in JSON. I need to merge them into one.
The 2 objects have in common this two key/value IRBId = ... and id = ... and they look as an example
OBJ 1
{
"data":{
"IRBs":{
"nodes":[
{
"id":"8",
"name":"Admin ",
},
{
"id":"9",
"name":"Again",
}
],
}
}
}
OBJ 2
{
"data":{
"informedConsentForms":{
"count":3,
"nodes":[
{
"id":"93",
...
"IRBId":"9",
},
{
"id":"92",
...
"IRBId":"8",
},
{
"id":"91",
...
"IRBId":"8",
}
],
}
},
As you will see above OBJ 2 and OBJ 1 corresponding with the same at IRBid and id.
What I need is to merge the two OBJ where IRBId OBJ 2 === id OBJ 1
The result I would expect after the merge is
OBJ merged
{
[{
"id":"93",
...
"IRBId":"9",
"irb": {
"name":"Again ",
...
}
},
{
"id":"92",
...
"IRBId":"8",
"irb": {
"name":"Admin ",
...
}
},
{
"id":"91",
...
"IRBId":"8",
"irb": {
"name":"Admin ",
...
}
],
},
I don't know how to make it looks like this.
Try using Array.reduce
Logic
Loop through second object data nodes
Find the matching nodes from object 1 data nodes.
Push to accumulator with required details. (I have added only the nodes that was mentioned in in Expected resut, you can add asmuch as you need.)
const obj1 = {
"data": {
"IRBs": {
"nodes": [
{
"id": "8",
"name": "Admin ",
},
{
"id": "9",
"name": "Again",
}
],
}
}
}
const obj2 = {
"data": {
"informedConsentForms": {
"count": 3,
"nodes": [
{
"id": "93",
"IRBId": "9",
},
{
"id": "92",
"IRBId": "8",
},
{
"id": "91",
"IRBId": "8",
}
],
}
},
};
const obj1List = obj1.data.IRBs.nodes;
const output = obj2.data.informedConsentForms.nodes.reduce((acc, curr) => {
const matchingNode = obj1List.find((item) => item.id === curr.IRBId);
if (matchingNode) {
acc.push({
id: curr.id,
IRBId: curr.IRBId,
irb: {
name: matchingNode.name
}
})
}
return acc;
}, []);
console.log(output);
You need to use the map function on the nodes in the first object to construct a new object that contains the second and first object's attributes.
const obj1 = {
"data": {
"IRBs": {
"nodes": [{
"id": "8",
"obj1": "one",
"name": "Admin ",
},
{
"id": "9",
"obj1": "two",
"name": "Again",
}
]
}
}
};
const obj2 = {
"data": {
"informedConsentForms": {
"count": 3,
"nodes": [{
"id": "93",
"obj2": "1",
"IRBId": "9",
},
{
"id": "92",
"obj2": "2",
"IRBId": "8",
},
{
"id": "91",
"obj2": "3",
"IRBId": "8",
}
],
}
}
};
const obj1Data = obj1.data.IRBs.nodes;
const obj2Data = obj2.data.informedConsentForms.nodes;
const res = obj2Data.map(item => {
const obj1Item = obj1Data.find(obj1Item => item.IRBId === obj1Item.id);
return obj1Item ? { ...item, "irb": { ...obj1Item}} : { ...item};
});
console.log(res);
i am using nested loop, try this one
const obj2 = {
"data":{
"informedConsentForms":{
"count":3,
"nodes":[
{
"id":"93",
"IRBId":"9",
},
{
"id":"92",
"IRBId":"8",
},
{
"id":"91",
"IRBId":"8",
}
],
}
},
}
const obj1 = {
"data":{
"IRBs":{
"nodes":[
{
"id":"8",
"name":"Admin ",
},
{
"id":"9",
"name":"Again",
}
],
}
}
}
const result = [];
const obj2Nodes = obj2.data.informedConsentForms.nodes;
for(let i = 0; i < obj2Nodes.length; i++) {
const obj1Nodes = obj1.data.IRBs.nodes
for(let j = 0; j < obj1Nodes.length; j++) {
if(obj2Nodes[i].IRBId === obj1Nodes[j].id) {
const {id, ...reObj1Nodes} = obj1Nodes[j];
result.push({
...obj2Nodes[i],
'irb': {
...reObj1Nodes
}
})
}
}
}
console.log(result)

Parent and Child array sorting doesn't working accordingly

I have list of tree node metadataList Like below:
[
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
}
]
},
{
"data": {
"metadata": {
"category": [
"Isv"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Isv"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Isv"
]
}
},
"children": [
]
}
]
},
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
]
}
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Isv"
]
}
},
"children": [
]
}
]
},
{
"data": {
"metadata": {
"category": [
"Incentives"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Incentives"
]
}
},
"children": [
]
}
]
}
]
Which is a type of array of data and children collection it's class like below:
export default class CurrentTopicMetadataTreeNode {
public data: CurrentTopicMetadata;
public children: CurrentTopicMetadataTreeNode[];
}
export default class CurrentTopicMetadata {
public id: string;
public metadata: TopicMetadata
}
export class TopicMetadata {
public category: Category[]
}
export enum Category {
Csp = 'Csp',
Mpn = 'Mpn',
Incentives = 'Incentives',
Referrals = 'Referrals',
Isv = 'Isv',
}
What I am trying, to filter list as data and children order as per category. Let say if filter by a category all data and children belongs to that category should come like below order.
But I am getting data like this order :
One Element On Array Problem Set:
Here in this array if I search with Csp Only data in root node which is Csp and data in children only has one data which contains Csp would be in array.
[{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
},
{
"data": {
"metadata": {
"category": [
"Mpn"
]
}
},
"children": [
]
}
]
}]
Expected Output: So after filtered by Csp node should be look like this:
[
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
{
"data": {
"metadata": {
"category": [
"Csp"
]
}
},
"children": [
]
}
]
}
]
here is my code, where I am doing wrong?
// Rule 1 check parent metadata category whether be empty
// Rule 2 and 3
function find_in_children(children, parent_category) {
children_has_same_category = []
for(var i in children) {
let child = children[i];
if(child.children != undefined && child.children.length > 0 && child.data.metadata.category == parent_category) {
children_has_same_category.push(child);
}
}
if(children_has_same_category.length > 0) {
return children_has_same_category
} else {
for(var i in children) {
let child = children[i];
return find_in_children(child.children, parent_category);
}
}
}
function check_object(object) {
let parent_category = object.data.metadata.category[0];
if(object.children != undefined && object.children.length > 0) {
return {'data': object.data, 'children': find_in_children(object.children, parent_category)}
} else {
return {'data': object.data}
}
}
function apply_rules(object) {
// Rule 1 check parent metadata category whether be empty
if(object.data.metadata.category.length > 0) {
return {'data': object.data}
} else {
return check_object(object)
}
}
target = {
value: 'Isv'
}
filtered_datas = []
for(var i in datas) {
let data = datas[i];
if(data.data.metadata.category.length > 0) {
result = apply_rules(data)
if(result.data.metadata.category[0] == target.value) {
filtered_datas.push(result);
}
}
}
Here is the data sample and result: https://jsfiddle.net/faridkiron/b02cksL8/#&togetherjs=F7FK3fBULx
Another Recursive Function I have tried:
handleRecursiveParentChildNode(parent: CurrentTopicMetadataTreeNode, searchKey) {
let result = parent;
result.children = [];
if (parent.children.length > 0) {
parent.children.forEach(child => {
let childData = this.handleRecursiveParentChildNode(child, searchKey);
if (childData.data && childData.data != undefined)
result.children.push(childData);
});
let cate = parent.data.metadata.category.filter(cat => cat === searchKey);
if (!result.children && cate.length < 1) {
result = null;
}
}
else {
let cate = parent.data.metadata.category.filter(cat => cat === searchKey);
if (cate.length < 1) {
result = null;
}
}
return result;
}
You can filter the data with Array.prototype.filter
const data = [{"data":{"metadata":{"category":["Csp"]}},"children":[{"data":{"metadata":{"category":["Csp"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]}]},{"data":{"metadata":{"category":["Isv"]}},"children":[{"data":{"metadata":{"category":["Isv"]}},"children":[]},{"data":{"metadata":{"category":["Isv"]}},"children":[]}]},{"data":{"metadata":{"category":["Csp"]}},"children":[{"data":{"metadata":{"category":["Csp"]}},"children":[]}]},{"data":{"metadata":{"category":["Mpn"]}},"children":[{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Csp"]}},"children":[]},{"data":{"metadata":{"category":["Isv"]}},"children":[]}]},{"data":{"metadata":{"category":["Incentives"]}},"children":[{"data":{"metadata":{"category":["Incentives"]}},"children":[]}]}]
const dfs = (iNode, type) => {
const node = Object.assign({}, iNode) // shallow copy current node
node.children = iNode.children.flatMap(child => {
// if child matches type, return it, otherwise filter it out
return child.data.metadata.category.includes(type) ? dfs(child, type) : []
})
return node
}
// fakes a root node to apply dfs on
const cspList = dfs({ children: data }, 'Csp').children
console.log(JSON.stringify(cspList, null, 2))
edit: in case flatMap can't be used (for some reasons) it is possible to use filter + map
const data = [{"data":{"metadata":{"category":["Csp"]}},"children":[{"data":{"metadata":{"category":["Csp"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]}]},{"data":{"metadata":{"category":["Isv"]}},"children":[{"data":{"metadata":{"category":["Isv"]}},"children":[]},{"data":{"metadata":{"category":["Isv"]}},"children":[]}]},{"data":{"metadata":{"category":["Csp"]}},"children":[{"data":{"metadata":{"category":["Csp"]}},"children":[]}]},{"data":{"metadata":{"category":["Mpn"]}},"children":[{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Mpn"]}},"children":[]},{"data":{"metadata":{"category":["Csp"]}},"children":[]},{"data":{"metadata":{"category":["Isv"]}},"children":[]}]},{"data":{"metadata":{"category":["Incentives"]}},"children":[{"data":{"metadata":{"category":["Incentives"]}},"children":[]}]}]
const dfs = (iNode, type) => {
const node = Object.assign({}, iNode) // shallow copy current node
node.children = iNode.children
.filter(child => child.data.metadata.category.includes(type))
.map(child => dfs(child, type))
return node
}
// fakes a root node to apply dfs on
const cspList = dfs({ children: data }, 'Csp').children
console.log(JSON.stringify(cspList, null, 2))
regarding errors in original code
handleRecursiveParentChildNode(parent: CurrentTopicMetadataTreeNode, searchKey) {
let result = parent;
result.children = [];
// bug? since we are called from parent we obviously are a children
// so else block is never run
if (parent.children.length > 0) {
parent.children.forEach(child => {
let childData = this.handleRecursiveParentChildNode(child, searchKey);
// bug ? we never filter out children and push every one of them on result
if (childData.data && childData.data != undefined)
result.children.push(childData);
});
// bug ? !result.children is never truthy (![] === false)
// so if is never run
let cate = parent.data.metadata.category.filter(cat => cat === searchKey);
if (!result.children && cate.length < 1) {
result = null;
}
}
// never run
else {
let cate = parent.data.metadata.category.filter(cat => cat === searchKey);
if (cate.length < 1) {
result = null;
}
}
return result;
}
You can use recursive call reduce for filter your data:
const filterItems = (items, f) => {
const fitems = items.reduce((acc, rec) => {
const children = rec.children.length > 0 ? filterItems(rec.children, f): []
if (rec.data.metadata.category.indexOf(f) >= 0) {
return [...acc, {...rec, children}]
}
return [...acc]
}, [])
return fitems
}

Modify javascript object to specific format

let data = {
"rec": [{
"id": "25837",
"contentId": "25838"
},
{
"id": "25839",
"contentId": "25838"
},
{
"id": "25838"
},
{
"id": "25636",
"contentId": "25837"
}, {
"id": "25640",
"contentId": "25839"
}
]
};
I have a javascript object which I have to manipulate to below format.
{
"childern": [{
"id": "25838",
"childern": [{
"id": "25837",
"contentId": "25838",
"childern": [{
"id": "25636",
"contentId": "25837"
}]
},
{
"id": "25839",
"contentId": "25838",
"childern": [{
"id": "25640",
"contentId": "25839"
}]
}
]
}]
}
If any object dont have contentId it should be at parent level. then all the objects having contentId same as parent id should be at its child level and so on.
I have created a fiddle here but logic is not completed. Any idea or reference to achieve this.
You could create recursive function with reduce method to get the desired result.
let data = {"rec":[{"id":"25837","contentId":"25838"},{"id":"25839","contentId":"25838"},{"id":"25838"},{"id":"25636","contentId":"25837"},{"id":"25640","contentId":"25839"}]}
function nest(data, pid) {
return data.reduce((r, e) => {
if (pid == e.contentId) {
const obj = { ...e }
const children = nest(data, e.id);
if (children.length) obj.children = children
r.push(obj)
}
return r;
}, [])
}
const result = nest(data.rec);
console.log(result[0])

How to get index of a nested field in JSON object using Loadash?

I have JSON object that looks like the snippet below.
I can navigate to the fields that I am interested to query, which match the pattern useful*
contents[0].mainArea[1].rec[0].attributes['useful.k1']
contents[0].mainArea[1].rec[1].attributes['useful.k1']
Is it possible to write a Lodash command that can extract/return the indices in bold above? I need the indices as I will later use these indices to create an assertion.
I am not sure where to start looking. Any ideas as to which commands I need to consider will be greatly appreciated.
{
"contents": [
{
"leftArea": [],
"mainArea": [
{
"key11": "val11",
"key12": [
{
"subkey11": "subValue11",
"subkey12": "subValue12"
},
{
"subkey21": "subValue21",
"subkey22": "subValue22"
}
]
},
{
"key21": "val21",
"rec": [
{
"attributes": {
"useful.k1": [
"value"
],
"useful.k2": [
"value"
],
"useful.k3": [
"value"
]
}
},
{
"attributes": {
"useful.k1": [
"value"
],
"useful.k2": [
"value"
],
"useful.k3": [
"value"
]
}
},
{
"notuseful": "value",
"notuseful2": [
{
"key1": "value",
"key2": "value"
},
{
"key1": "value",
"key2": "value"
},
{
"key1": "value",
"key2": "value"
}
]
}
]
}
]
}
]
}
I don't know if there is any specific lodash command for your problem, but you can use some functions to find your desired indices by some code like below:
const keys = require('lodash/keys');
function findUsefulKeys(data) {
const indices = [];
for (let i = 0; i < data.contents.length; i++) {
const mainAreaIndiesWithUsefulKeys = findUsefulKeysInContent(data.contents[i]);
if (mainAreaIndiesWithUsefulKeys.length > 0) {
indices.push({
contentIndex: i,
mainAreaIndices: mainAreaIndiesWithUsefulKeys
});
}
}
return indices;
}
function findUsefulKeysInContent(content) {
const mainAreaIndices = [];
if (!content.mainArea || !content.mainArea.length || content.mainArea.length === 0) {
return mainAreaIndices;
}
for (let i = 0; i < content.mainArea.length; i++) {
const recIndices = findRecsWithUsefulKeysInMainArea(content.mainArea[i]);
if (recIndices.length > 0) {
mainAreaIndices.push({
mainAreaIndex: i,
recIndices: recIndices
});
}
}
return mainAreaIndices;
}
function findRecsWithUsefulKeysInMainArea(mainArea) {
const recIndices = [];
if (!mainArea.rec || !mainArea.rec.length || mainArea.rec.length === 0) {
return recIndices;
}
for (let i = 0; i < mainArea.rec.length; i++) {
if (recHasUsefulKeys(mainArea.rec[i])) {
recIndices.push(i);
}
}
return recIndices;
}
function recHasUsefulKeys(rec) {
if (!rec || !rec.attributes) {
return false;
}
const attributeKeys = keys(rec.attributes);
for (let i = 0; i < attributeKeys.length; i++) {
if (attributeKeys[i].startsWith('useful')) {
return true;
}
}
return false;
}
// data is you json data
const data = require('./data');
console.log(JSON.stringify(findUsefulKeys(data)));
The above code with your example json data will print
[{"contentIndex":0,"mainAreaIndices":[{"mainAreaIndex":1,"recIndices":[0,1]}]}]
You can loop to get the value from JSON like below:
const data = {
"contents": [
{
"leftArea": [],
"mainArea": [
{
"key11": "val11",
"key12": [
{
"subkey11": "subValue11",
"subkey12": "subValue12"
},
{
"subkey21": "subValue21",
"subkey22": "subValue22"
}
]
},
{
"key21": "val21",
"rec": [
{
"attributes": {
"useful.k1": [
"value"
],
"useful.k2": [
"value"
],
"useful.k3": [
"value"
]
}
},
{
"attributes": {
"useful.k1": [
"value"
],
"useful.k2": [
"value"
],
"useful.k3": [
"value"
]
}
},
{
"notuseful": "value",
"notuseful2": [
{
"key1": "value",
"key2": "value"
},
{
"key1": "value",
"key2": "value"
},
{
"key1": "value",
"key2": "value"
}
]
}
]
}
]
}
]
}
const useful = data.contents[0].mainArea[1].rec;
for(const v in useful)
{
//console.log(useful[v].attributes); // Oject
for( let vv in useful[v].attributes) // for for every single value
{
console.log(useful[v].attributes[vv]);
}
}

How can i get nested json objects values in single key value pair array

I have nested json Objects Array and i want to get values from that nested Array in the format of single key value pair to pass to http call instead of single object i need array
[{
"menu_id":1,
"parent_menu_id":0,
"children":[
{
"menu_id":2,
"parent_menu_id":1,
"children":[
{
"menu_id":3,
"parent_menu_id":2,
"children":[
]
}
]
}
]
},
{
"menu_id":5,
"parent_menu_id":0,
"children":[
{
"menu_id":6,
"parent_menu_id":5,
"children":[
{
"menu_id":7,
"parent_menu_id":6,
"children":[
]
}
]
}
]
}]
I need result in the array format
[
{
"menu_id":1,
"parent_menu_id":0
},
{
"menu_id":2,
"parent_menu_id":1
},
{
"menu_id":3,
"parent_menu_id":2
}
]
You can create a recursive function for that.
function toFlatArray(obj) {
let result = [];
result.push({
menu_id: obj.menu_id,
parent_menu_id: obj.parent_menu_id
});
if (obj.children && obj.children.length) {
obj.children.forEach(child => {
result = result.concat(toFlatArray(child));
});
}
return result;
}
// added another method to run forEach
function toFlatArrayFromArray(arr) {
let result = [];
arr.forEach(obj => {
result = result.concat(toFlatArray(obj));
});
return result;
}
const myObj = [{
"menu_id":1,
"parent_menu_id":0,
"children":[
{
"menu_id":2,
"parent_menu_id":1,
"children":[
{
"menu_id":3,
"parent_menu_id":2,
"children":[
]
}
]
}
]
},
{
"menu_id":5,
"parent_menu_id":0,
"children":[
{
"menu_id":6,
"parent_menu_id":5,
"children":[
{
"menu_id":7,
"parent_menu_id":6,
"children":[
]
}
]
}
]
}];
console.log(toFlatArrayFromArray(myObj));

Categories

Resources