Flatten/Reduce nested JSON based on paths, collecting fields in path/hierarchy - javascript

UPDATE: 11/18/18: I've added more examples and clarification to the end of this post.
I have nested JSON structures, resulting from Elasticsearch aggregations, that look similar to this (simplified for the example):
{
"some_field": "ignore",
"buckets": [
{
"key": "a",
"buckets": [
{ "key": "foo", "name": "FOO" },
{ "key": "bar", "name": "BAR" }
]
},
{
"key": "b",
"buckets": [
{ "key": "boo", "name": "BOO" },
{ "key": "baa", "name": "BAA" }
]
}
]
}
I would like to convert it to
[
{key: "a", name: "FOO"},
{key: "a", name: "FOO"},
{key: "b", name: "BOO"},
{key: "b", name: "BAA"}
]
While this is a simple problem for any specific case, I don't want to "re-invent the wheel" and code for it each time. The level of nesting can vary and the fields I might pick (along the path) can vary.
The above is somewhat simplified example. As these come from Elasticsearch responses, another example might be:
"aggregations": {
"boo": {
"buckets": [
{
"key": "keyA",
"foo": {
"buckets": [
{
"key": "keyA.a",
"bar": {
"hits": {
"hits": [{"_index": "indexA", "_id": "idA", "_source": {"name": "nameA"}}]
}
}
}
]
}
},
{
"key": "keyB",
"foo": {
"buckets": [
{
"key": "keyA.a",
"bar": {
"hits": {
"hits": [{"_index": "indexB", "_id": "idB", "_source": {"name": "nameB"}}]
}
}
}
]
}
}
]
}
}
and my desired result, for flatten/picking fields is
[
{"boo": "keyA", "foo": "keyA.a", "name": "nameA", "id": "idA"},
{"boo": "keyB", "foo": "keyA.a", "name": "name", "id": "idB"}
]
Does anyone know of a way to do with JMESPath, JSONPath, lodash, etc? With something like JSONPath or JMESPath, I can select the "leaf" value easily, but I am trying to collect fields along the path, in the hierachy.
As noted, I could code each case, but I'd like to reuse a library and declare my projection.
I think I could do this with jq, but I need it to run in a browser.
Thanks

Lodash is useful in your case and can be used in a browser.
flapMap and map functions are what you need.
const _ = require('lodash');
var obj = {
"some_field": "ignore",
"buckets": [
{
"key": "a",
"buckets": [
{ "key": "foo", "name": "FOO" },
{ "key": "bar", "name": "BAR" }
]
},
{
"key": "b",
"buckets": [
{ "key": "boo", "name": "BOO" },
{ "key": "baa", "name": "BAA" }
]
}
]
};
var res = _.flatMap(obj.buckets, x => {
return _.map(x.buckets, y => {
y.key = x.key;
return y;
});
});
console.log(res);
// Output:
// [ { key: 'a', name: 'FOO' },
// { key: 'a', name: 'BAR' },
// { key: 'b', name: 'BOO' },
// { key: 'b', name: 'BAA' }]

Related

how to change the format of json array by loping over

Hi I am getting data from API but I want my data in different format so that I can pass later into a function. I want to change the names of keys into a different one becasuse I have created a chart and it only draws if I send it data in certain way
This is what I am getting from API
data = {
"status": "success",
"from": "DB",
"indice": "KSE100",
"data": [
{
"stock_sector_name": "Tc",
"sector_score": "0828",
"stocks": [
{
"stock_symbol": "TRG",
"stock_score": 44
},
{
"stock_symbol": "SYS",
"stock_score": 33
}
]
},
{
"stock_sector_name": "OIL",
"sector_score": "0828",
"stocks": [
{
"stock_symbol": "FFS",
"stock_score": 44
},
{
"stock_symbol": "SMS",
"stock_score": 33
}
]
},
]
}
But I want my data to look like this like this
data = {
"name": "KSE100",
"children": [
{
"name": "A",
'points': -9,
"children": [
{
"stock_title": "A",
"value": 12,
},
{
"stock_title": "B",
"value": 4,
},
]
},
{
"name": "B",
'points': 20,
"children": [
{
"stock_title": "A",
"value": 12,
},
{
"name": "B",
"value": 4,
},
]
},
]
}
Like I want to replace
stock_sector_name = name
sector_score = value
stocks = children
stock_symbol = name
stock_score = value
I have been trying this for so much time but sill could not figured it out
function convert(d){
return {
name : d.indice,
children : d.data.map(y=>{
return {
name : y.stock_sector_name,
points : y.sector_score,
children : y.stocks.map(z=>{
return {
stock_title: z.stock_symbol,
value : z.stock_score
}
})
}
})
}
}
You can do something like this
const data = {
"status": "success",
"from": "DB",
"indice": "KSE100",
"data": [{
"stock_sector_name": "Tc",
"sector_score": "0828",
"stocks": [{
"stock_symbol": "TRG",
"stock_score": 44
},
{
"stock_symbol": "SYS",
"stock_score": 33
}
]
},
{
"stock_sector_name": "OIL",
"sector_score": "0828",
"stocks": [{
"stock_symbol": "FFS",
"stock_score": 44
},
{
"stock_symbol": "SMS",
"stock_score": 33
}
]
},
]
}
const data2 = {
"name": "KSE100",
"children": [{
"name": "A",
'points': -9,
"children": [{
"stock_title": "A",
"value": 12,
},
{
"stock_title": "B",
"value": 4,
},
]
},
{
"name": "B",
'points': 20,
"children": [{
"stock_title": "A",
"value": 12,
},
{
"name": "B",
"value": 4,
},
]
},
]
}
//stock_sector_name = name
//sector_score = value
//stocks = children
//stock_symbol = stock_title
//stock_score = value
const keys = {
stock_sector_name: "name",
sector_score: "points",
stocks: "children",
stock_symbol: "stock_title",
stock_score: "value",
indice: "name",
//data: "children"
}
const rename = (value) => {
if (!value || typeof value !== 'object') return value;
if (Array.isArray(value)) return value.map(rename);
return Object.fromEntries(Object
.entries(value)
.map(([k, v]) => [keys[k] || k, rename(v)])
);
}
renamedObj = rename(data);
console.log(renamedObj);

Look up values in an array using looping forEach Google Apps Script Javascript

I have an object that looks like the following {key: id numbers}
var obj = {
"c4ecb": {id: [3]},
"a4269": {id: [34,36]},
"d76fa": {id: [54,55,60,61]},
"58cb5": {id: [67]}
}
How do I loop each above id in the following array, and return the label?
var response =
{
"success": true,
"data": [
{
"key": "c4ecb",
"name": "fruits",
"options": [
{
"label": "strawberry",
"id": 3
},
{
"label": "apple",
"id": 4
},
{
"label": "pineapple",
"id": 5
},
{
"label": "Other",
"id": 31
}
],
}
]
},
{
"success": true,
"data": [
{
"key": "a4269",
"name": "vegetables",
"options": [
{
"label": "lettuce",
"id": 34
},
{
"label": "cucumber",
"id": 35
},
{
"label": "radish",
"id": 36
}
],
}
]
},
{
"success": true,
"data": [
{
"key": "d76fa",
"name": "pasta",
"options": [
{
"label": "spaghetti",
"id": 54
},
{
"label": "rigatoni",
"id": 55
},
{
"label": "linguine",
"id": 56
},
{
"label": "lasagna",
"id": 60
},
{
"label": "fettuccine",
"id": 61
}
],
}
]
}
Finally, what I want to do is look up the key and return a string of id values.
For example, input c4ecb and output strawberry. Input a4269 and output lettuce, radish. Input d76fa and output "spaghetti, rigatoni, lasagna, fettuccine"
I think to join the multiple labels output into one string I could use something like
array.data.vegetables.map(vegetables => vegetables.value).join(', ')].toString();
So in the end I want to have something like
var fruits = [some code that outputs "strawberry"];
var vegetables = [some code that outputs "lettuce, radish"];
var pasta = [some code that outputs "spaghetti, rigatoni, lasagna, fettuccine"];
What I've tried so far:
The following loop will return the id only if there is one id to be called for: e.g. only in case one where {id: 3} but returns null in cases like {id: 34,36} (because it's looking for '34,36' in id, which doesn't exist - I need to look for each one individually.
response.data.forEach(({key, options}) => {
if (obj[key]) {
options.forEach(({id, label}) => {
if (id == obj[key].id) obj[key].label = label;
});
}
});
console.log(obj)
Filter the response object to focus on the category that matches the id.
Map over the options array and select the items which appear in obj[id].
Finally convert the filtered results to a string.
See filteredLabelsAsString() function below for implementation.
var obj = {
"c4ecb": {"id": [3]},
"a4269": {"id": [34,36]},
"d76fa": {"id": [54,55,60,61]},
"58cb5": {"id": [67]}
}
var response =
[{
"success": true,
"data": [
{
"key": "c4ecb",
"name": "fruits",
"options": [
{
"label": "strawberry",
"id": 3
},
{
"label": "apple",
"id": 4
},
{
"label": "pineapple",
"id": 5
},
{
"label": "Other",
"id": 31
}
],
}
]
},
{
"success": true,
"data": [
{
"key": "a4269",
"name": "vegetables",
"options": [
{
"label": "lettuce",
"id": 34
},
{
"label": "cucumber",
"id": 35
},
{
"label": "radish",
"id": 36
}
],
}
]
},
{
"success": true,
"data": [
{
"key": "d76fa",
"name": "pasta",
"options": [
{
"label": "spaghetti",
"id": 54
},
{
"label": "rigatoni",
"id": 55
},
{
"label": "linguine",
"id": 56
},
{
"label": "lasagna",
"id": 60
},
{
"label": "fettuccine",
"id": 61
}
],
}
]
}];
function filteredLabelsAsString(obj_key, obj, content=response) {
// sanity check: obj must contain obj_key
if (Object.keys(obj).includes(obj_key)) {
return content.filter((item) => {
// filter content using value of obj_key
return item.data[0].key == obj_key;
}).map((item) => {
// item : { success: true, data: [] }
// map over options array
return item.data[0].options.map((opt) => {
// option : {id, label}
// return the label if the id is in the obj object's list
if (obj[item.data[0].key].id.includes(opt.id))
return opt.label;
}).filter((label) => {
// filter out empty items
return label !== undefined;
});
}).join(",");
}
// if obj does not contain obj_key return empty string
return "";
}
console.log("fruits: " + filteredLabelsAsString("c4ecb", obj));
console.log("vegetables: " + filteredLabelsAsString("a4269", obj));
console.log("pasta: " + filteredLabelsAsString("d76fa", obj));

How to filter a complex object based on another complex object property

So there is an complex Object A
{
"AId": 34,
"children": [
{
"id": 1,
"name": "name1",
"prop": "Hello"
},
{
"id": 2,
"name": "name2",
"prop": "world"
}
]
}
and there is complex Object B
[
{
"id": 1,
"name": "name1"
},
{
"id": 2,
"name": "name2"
}
]
I want to remove all elements in object B which are in Object A children just based on id. So A object children have id 1 & 2 in example which are also in object B so those id 1 & 2 needs to be removed in object B,what is the best way to do this? hopefully i explained this well
try with foreach
oB= [
{
"id": 1,
"name": "name1"
},
{
"id": 2,
"name": "name2"
}
]
oA={
"AId": 34,
"children": [
{
"id": 1,
"name": "name1",
"prop": "Hello"
},
{
"id": 2,
"name": "name2",
"prop": "world"
}
]
}
oA.children.forEach(x=>{
oB.forEach(y=>{
if(x.id==y.id) oB.shift()
})
})
console.log(oB)
A.children.forEach(x=>{
this.B.forEach(y=>{
if(x.id==y.id)
{
this.A.children = this.A.children.filter(s => s.id !== x.id);
}
})
});

How to dynamically build a data structure in pure JavaScript?

Is there any plugin that allow to dynamically build following data structure in plain JavaScript object or JSON?
This is the structure I would like to implement:
Check my plunker (https://plnkr.co/edit/1eWjXLPumuOMhv8BjaNw?p=preview)
{
"name": "Select0",
"id": "select0",
"values": [
"Option1",
"Option2"
],
"dependent": {
"name": "Select1",
"id": "select1",
"values": {
"Option1": [
"Option11",
"Option12"
],
"Option2": [
"Option21",
"Option22"
]
},
"dependent": {
"name": "Select2",
"id": "select2",
"values": {
"Option1": {
"Option11": [
"Option111",
"Option112"
],
"Option12": [
"Option121",
"Option122"
]
},
"Option2": {
"Option21": [
"Option211",
"Option212"
],
"Option22": [
"Option221",
"Option222"
]
}
},
"dependent": []
}
}
}
Note that you can set values on properties on an object and then print it as a JSON.
var obj = {};
object.name = "Select0";
// Set more properities.
// Stringify object to JSON format looking as the one in the question.
var json = JSON.stringify(obj, null, 2);
// Verify by printing on the screen.
console.log(json);

Merge two javascript objects without overwriting

I have two javascript object with data like this:
{
"data": [
{
"foo": "2388163605_10150954953808606",
"bar": {
"xyz": "123",
},
"name": {
"name": [
{
"content": 1,
"data": "some text",
"id": "2388163605"
}
]
},
},
{
"not_the_same": "other values",
"foo": "different",
"bar": {
"xyz": "123",
},
"name": {
"name": [
{
"content": 1,
"data": "some text",
"id": "2388163605"
}
]
},
}]
}
Second one:
{
"data": [
{
"foo": "123+09",
"bar": {
"xyz": "1adad0",
},
},
}]
}
as you can see, the properties etc vary a bit, and of course the values as well. Also they don't have the same number of items in them. I'm trying to merge them like this:
$.extend(true,posts, posts_object);
(posts and posts_objects contain the two objects)
I want to merge them into this:
{
"data": [
{
"foo": "2388163605_10150954953808606",
"bar": {
"xyz": "123",
},
"name": {
"name": [
{
"content": 1,
"data": "some text",
"id": "2388163605"
}
]
},
},
{
"not_the_same": "other values",
"foo": "different",
"bar": {
"xyz": "123",
},
"name": {
"name": [
{
"content": 1,
"data": "some text",
"id": "2388163605"
}
]
},
},
{
"foo": "123+09",
"bar": {
"xyz": "1adad0",
},
},
}]
}
However, this results in the data from the second object to overwrite the data of the first object. Is there any way to merge them but instead add the items from the second object to the first object?
If by "add the items", you mean that you want all properties that are in the second object but not in the first to end up being added to the first one, then:
var tmp;
$.extend(true, tmp, posts);
$.extend(true, posts, posts_object);
$.extend(true, posts, tmp);
might work. It all depends on what copying semantics you want.

Categories