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