Get values from nested object with dynamic ids - javascript - javascript

I'm recieving a payload from SlackAPI (block elements) and I can't get my head around oh how do I get data from it as ids and order always change.
I want to get protection_fee.value, legal_fee.value and repayment_date.selected_date
"state": {
"values": {
"CjV": {
"protection_fee": {
"type": "plain_text_input",
"value": "111"
}
},
"36tAM": {
"legal_fee": {
"type": "plain_text_input",
"value": "111"
}
},
"oH8": {
"repayment_date": {
"type": "datepicker",
"selected_date": "1990-04-18"
}
}
}
},
I tried Object.keys but obviously I failed as order changes.
current code:
const payload = JSON.parse(body);
const state = payload.state.values;
const first = Object.keys(state)[0];
const second = Object.keys(state)[1];
const repaymentDate = state[first].protection_fee.value;
const protectionFee = state[second].legal_fee.value;

I would suggest creating a function like findProperty() that will find the relevant object in the payload.
We'd call Object.entries() on the payload.state.values object and then using Array.find() on the entry key/value pairs to find an object with the desired property.
Once we have the property we can return it.
let payload = { "state": { "values": { "CjV": { "protection_fee": { "type": "plain_text_input", "value": "111" } }, "36tAM": { "legal_fee": { "type": "plain_text_input", "value": "111" } }, "oH8": { "repayment_date": { "type": "datepicker", "selected_date": "1990-04-18" } } } } }
function findProperty(obj, key) {
const [, value] = Object.entries(obj).find(([k,v]) => v[key]);
return value[key];
}
console.log('legal_fee:', findProperty(payload.state.values, 'legal_fee').value)
console.log('protection_fee:', findProperty(payload.state.values, 'protection_fee').value)
.as-console-wrapper { max-height: 100% !important; }

Related

How to parse FractalTransformer with normalizr

I'm trying to use paularmstrong/normalizr on JSON that comes from FractalTransformer and whose nested childs have "data" attribute. Example of JSON:
{
"data": {
"object": "Offer",
"id": "5g6aqocew4qjzl40",
"real_id": 26,
"name": "Random Name",
"created_at": {
"date": "2019-06-18 11:13:08.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"readable_created_at": "1 year ago",
"site": {
"data": {
"object": "Site",
"id": "65zody8vj29vlegd",
"name": "Test Site",
"real_id": 1
}
},
"countries": {
"data": [
{
"object": "Country",
"code": "US",
"name": "United States"
},
{
"object": "Country",
"code": "DE",
"name": "Germany"
}
]
}
},
"meta": {
"include": [
"site",
"countries"
],
"custom": []
}
}
Schemas I use:
export const offerSchema = new schema.Entity('offers')
export const siteSchema = new schema.Entity('sites', {}, {
processStrategy: (value) => {
return { ...value.data }
},
idAttribute: (value) => {
return value.data.id
},
})
export const countrySchema = new schema.Entity('countries')
offerSchema.define({
site: siteSchema,
countries: [countrySchema],
})
Now the issue is that I remove 'data' from the site since it's just one object successfully, but I can't do it in the country case. Whatever I tried with custom processStrategy fails, as country is object that has data which is array (I assume this is where the issue is, going from Entity to Array). And in idAttribute function I always get complete array so can't determine the ID of single entry. So the end result is that the ID of countries is undefined. Any ides?
I actually managed with another approach. I added processStrategy on the parent, 'Offer' in this case, so all 'data' parts get stripped before they reach other child schemas.
const normalizrStripDataOptions = {
processStrategy: (value) => {
const ret = { ...value }
Object.keys(ret).forEach((key) => {
if (ret[key] !== null) {
if (ret[key].data && Array.isArray(ret[key].data)) {
ret[key] = [...ret[key].data]
}
if (ret[key].data && typeof ret[key].data === 'object') {
ret[key] = { ...ret[key].data }
}
}
})
return ret
},
}
export const offerSchema = new schema.Entity('offers', {}, normalizrStripDataOptions)
export const siteSchema = new schema.Entity('sites')
export const countrySchema = new schema.Entity('countries')
offerSchema.define({
site: siteSchema,
countries: [countrySchema],
})

Adding new elements in JSON

I'm trying to add a new element inside of my JSON, but I'm not getting this done right.
I'd already tried with a lot of ways and not sure What is going on.
INPUT JSON
{
"UnitID":"1148",
"UNIT":"202B",
"Speed":"29.0",
"SpeedMeasureValue":"MPH",
"Direction":"212",
"LatitudeY":"42.0474150666667000",
"LongitudeX":"-88.2750256000000000",
"TimeStamp":"2019-04-25 15:19:45.0300000",
"Status":"Enroute","StatusID":"13",
"CallID":"841809",
"ORI":"IL0450600"
}
EXPECTED JSON OUTPUT AFTER CONCATENATION
{
"UnitID":"1148",
"UNIT":"202B",
"Speed":"29.0",
"SpeedMeasureValue":"MPH",
"Direction":"212",
"LatitudeY":"42.0474150666667000",
"LongitudeX":"-88.2750256000000000",
"TimeStamp":"2019-04-25 15:19:45.0300000",
"Status":"Enroute","StatusID":"13",
"CallID":"841809","ORI":"IL0450600",
"association": [
{
"event": "123",
"label": "",
"relation": "321"}
]
}
Code - Consider objectToJson as an array of input JSON and obj the input json mentioned before
objectToJson.forEach((obj: any) => {
const association: any = `"association": [{"event": 123, "label": "", "relation": "321"}]`;
const concatenatedObject: object = Object.assign(obj, association);
const concatEventsJson: any = JSON.stringify(concatenatedObject);
console.log(concatEventsJson);
}
Just add the association property to the object - you'll need to make association a variable (and not a string) though:
const association: any = [{"event": 123, "label": "", "relation": "321"}];
const concatenatedObject: object = { ...obj, association };
Demonstration:
const objectToJson = [{"UnitID":"1148","UNIT":"202B","Speed":"29.0","SpeedMeasureValue":"MPH","Direction":"212","LatitudeY":"42.0474150666667000","LongitudeX":"-88.2750256000000000","TimeStamp":"2019-04-25 15:19:45.0300000","Status":"Enroute","StatusID":"13","CallID":"841809","ORI":"IL0450600"}];
objectToJson.forEach((obj) => {
const association = [{"event": 123, "label": "", "relation": "321"}];
const concatenatedObject = { ...obj, association };
console.log(concatenatedObject);
});
.as-console-wrapper { max-height: 100% !important; top: auto; }

Javascript nested .map() returning multiple arrays

Given that I have a JSON structure like this:
{
"firstData": [{
"secondData": [{
"thirdData": [{
"value": "whatever"
}]
}]
}]
}
And I need to map from thirdData value === "whatever"
So I am doing
const result = firstData.map(first => {
return first.secondData.map(second => {
return second.thirdData.map(third => {
return third.value === 'whatever';
});
});
});
And this works somewhat fine, but the result is a another deeply nested array (like [ [ [ {results..} ] ] ]). I know I can flatten this to a single array by other means, but I feel like I am miss using .map(). How can I modify this result to a single array that contains the values of thirdData where the value is what ever I want?
The desired result for this would be a single array of thirdData objects:
[{ value: 'whatever'}, ... {n}]
You can use Array#reduce for reducing into a single value(in this case single array) and Array#forEach for iterating over the nested array.
const data = {
"firstData": [{
"secondData": [{
"thirdData": [{
"value": "whatever"
}]
}]
}]
}
const result = data.firstData.reduce((arr, first) => {
// iterate over the second level array
first.secondData.forEach(second => {
// iterate over the third level array
second.thirdData.forEach(third => {
// push the value into the result array,
// change here, in case you want the value
//arr.push(third.value === 'whatever');
// in case you need the object then do it like
if(third.value === 'whatever') arr.push(third);
});
});
// return the array reference for the next iteration
return arr;
// set the initial value as an array for the result
}, []);
console.log(result);
If you want a flat result, this isn't a use case for map. The simple solution is just to use an array you close over and push to:
const result = [];
firstData.forEach(first => {
return first.secondData.forEach(second => {
result.push(...second.thirdData.filter(third => third.value === 'whatever'));
});
});
Live Example with a slight extension to your minimal provided data:
const data = {
"firstData": [{
"secondData": [{
"thirdData": [{
"value": "whatever",
"label": "third #1.1"
},
{
"value": "whatever",
"label": "third #1.2"
},
{
"value": "unrelated",
"label": "unrelated"
}
]
}]
},
{
"secondData": [{
"thirdData": [{
"value": "another unrelated"
},
{
"value": "whatever",
"label": "third #2"
}
]
}]
}
]
};
const result = [];
data.firstData.forEach(first => {
return first.secondData.forEach(second => {
result.push(...second.thirdData.filter(third => third.value === 'whatever'));
});
});
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
Note the filter on the thirdData and using spread notation to push that data into result.
That assumes you want the entry from thirdData that has .value === 'whatever' rather than a true/false. If you want the true/false instead, change that filter to map.
Or the for-of equivalent:
const result = [];
for (const first of firstData) {
for (const second of first.secondData) {
result.push(...second.thirdData.filter(third => third.value === 'whatever'));
}
}
Live Example with a slight extension to your minimal provided data:
const data = {
"firstData": [{
"secondData": [{
"thirdData": [{
"value": "whatever",
"label": "third #1.1"
},
{
"value": "whatever",
"label": "third #1.2"
},
{
"value": "unrelated",
"label": "unrelated"
}
]
}]
},
{
"secondData": [{
"thirdData": [{
"value": "another unrelated"
},
{
"value": "whatever",
"label": "third #2"
}
]
}]
}
]
};
const result = [];
for (const first of data.firstData) {
for (const second of first.secondData) {
result.push(...second.thirdData.filter(third => third.value === 'whatever'));
}
}
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
(Same note about filter/map.)
As with all array operations, you can shoehorn this into reduce, and I guarantee you you'll get answers primarily using reduce, but there's no good reason to use reduce here.
const result = firstData.reduce((result, first) => {
return first.secondData.reduce((result, second) => {
result.push(...second.thirdData.filter(third => third.value === 'whatever'));
return result;
}, result);
}, []);
Again, though, there's no good reason for that. It's just more complicated.
Live Example with a slight extension to your minimal provided data:
const data = {
"firstData": [{
"secondData": [{
"thirdData": [{
"value": "whatever",
"label": "third #1.1"
},
{
"value": "whatever",
"label": "third #1.2"
},
{
"value": "unrelated",
"label": "unrelated"
}
]
}]
},
{
"secondData": [{
"thirdData": [{
"value": "another unrelated"
},
{
"value": "whatever",
"label": "third #2"
}
]
}]
}
]
};
const result = data.firstData.reduce((result, first) => {
return first.secondData.reduce((result, second) => {
result.push(...second.thirdData.filter(third => third.value === 'whatever'));
return result;
}, result);
}, []);
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
(Same note about filter/map.)

cannot update an array of elements via a 2d iteration

I have two arrays of object, the first array (printerChart, around 80 elements) is made of the following type of objects:
[{
printerBrand: 'Mutoh',
printerModel: 'VJ 1204G',
headsBrand: 'Epson',
headType: '',
compatibilty: [
'EDX',
'DT8',
'DT8-Pro',
'ECH',
],
},
....
]
The second array (items, around 500 elements) is made of the following type of objects:
[
{
"customData": {
"brand": {
"value": {
"type": "string",
"content": "hp"
},
"key": "brand"
},
"printer": {
"value": {
"type": "string",
"content": "c4280"
},
"key": "printer"
}
},
"name": "DT8 XLXL",
"image": {
"id": "zLaDHrgbarhFSnXAK",
"url": "https://xxxxxxx.net/images/xxxxxx.jpg"
},
"brandId": "xxxxx",
"companyId": "xxxx",
"createdAt": "2018-03-26T14:39:47.326Z",
"updatedAt": "2018-04-09T14:31:38.169Z",
"points": 60,
"id": "dq2Zezwm4nHr8FhEN"
},
...
]
What I want to do is to iterate via the second array and, if the part of the name of an item (i.e. DT8) is included in an element of the array 'compatibility' of the first array, I would like to include a new properties to it from the element of the first array: printerBrand. I have tried but somehow the iteration doesn't take place correctly. This is what I tried:
items.forEach((item) => {
printerChart.forEach((printer) => {
if (printer.compatibilty.some(compatibleElem => (
item.name.includes(compatibleElem)))) {
item.printerBrand = printer.printerBrand;
} else {
item.printerBrand = '';
}
});
});
What am I doing wrong?
You do
items.items.forEach(...)
Shouldn't you be doing
items.forEach(...)
?
I suggest to initialize item.printerBrand with an empty string and use a nested approach of some for getting a brand and to exit the loops, if found.
This prevents to get an empty string even if there is a brand to assign.
items.forEach((item) => {
item.printerBrand = '';
printerChart.some(printer => {
if (printer.compatibilty.some(compatibleElem => item.name.includes(compatibleElem))) {
item.printerBrand = printer.printerBrand;
return true;
}
});
});

Group and count values in an array

I have an array with objects, like the following.
b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
I want to count how many issues have status close, and how many have backlog. I'd like to save the count in a new array as follows.
a = [
{Name: 'Backlog', count: 1},
{Name: 'close', count: 2}
];
I have tried the following.
b.issues.forEach(function(i) {
var statusName = i.fields.status.name;
if (statusName in a.Name) {
a.count = +1;
} else {
a.push({
Name: statusName,
count: 1
});
}
});
That however doesn't seem to be working. How should I implement this?
This is a perfect opportunity to use Array#reduce. That function will take a function that is applied to all elements of the array in order and can be used to accumulate a value. We can use it to accumulate an object with the various counts in it.
To make things easy, we track the counts in an object as simply {name: count, otherName: otherCount}. For every element, we check if we already have an entry for name. If not, create one with count 0. Otherwise, increment the count. After the reduce, we can map the array of keys, stored as keys of the object, to be in the format described in the question. See below.
var b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
var counts = b.issues.reduce((p, c) => {
var name = c.fields.status.name;
if (!p.hasOwnProperty(name)) {
p[name] = 0;
}
p[name]++;
return p;
}, {});
console.log(counts);
var countsExtended = Object.keys(counts).map(k => {
return {name: k, count: counts[k]}; });
console.log(countsExtended);
.as-console-wrapper {
max-height: 100% !important;
}
Notes.
Array#reduce does not modify the original array.
You can easily modify the function passed to reduce to for example not distinguish between Backlog and backlog by changing
var name = c.fields.status.name;
into
var name = c.fields.status.name.toLowerCase();
for example. More advanced functionality can also easily be implemented.
Using ES6 Arrow functions you can do it with minimum syntax
var b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
var countOfBackLog = b.issues.filter(x => {
return x.fields.status.name === "Backlog"
}).length
var countOfClose = b.issues.filter(x => {
return x.fields.status.name === "close"
}).length
a =[{Name: 'Backlog', count : countOfBackLog}, {Name: 'close', count : countOfClose}]
More about arrow functions here
You can write like this. It is dynamic.
var a = {};
for(var key in b["issues"]){
if(!a.hasOwnProperty(b["issues"][key].fields.status.name)){
a[b["issues"][key].fields.status.name] = 1;
}else{
a[b["issues"][key].fields.status.name] = a[b["issues"][key].fields.status.name]+1;
}
}
var c = [];
for(var key1 in a){
c.push({
name : key1,
count : a[key1]
});
}
Something like this should do the trick. Simply iterate over your data, keep 2 counters with the number of each type of issue, and create the data format you want in the end. Try it live on jsfiddle.
var b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
var data = [];
for(var issue of b.issues){
var entryFound = false;
var tempObj = {
name: issue.fields.status.name,
count: 1
};
for(var item of data){
if(item.name === tempObj.name){
item.count++;
entryFound = true;
break;
}
}
if(!entryFound){
data.push(tempObj);
}
}
console.log(data);

Categories

Resources