Reducing nested array - reactjs

I am receiving this JSON from my backend and i need to work out the count of "concrete_compressive_cylinder_100"'s where picked_up = false
concrete_samples (can be multiple per work order) can be null ( key is always present )
sample_specimens ( 1 per concrete_sample) can be null ( key is always present )
concrete_compressive_cylinder_100 ( null to 500 per sample_specimens )
{
"uuid":"4ad7bfe1-48d6-488c-bfaf-33f7189a41d7",
"org_workorder_id":1000,
"concrete_samples":[
{
"uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"workorder_uuid":"4ad7bfe1-48d6-488c-bfaf-33f7189a41d7",
"org_sample_id":5001,
"sample_specimens":{
"concrete_compressive_cylinder_100":[
{
"uuid":"b9ef3a8a-2945-41e6-a34d-d90d1bd64819",
"sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"picked_up":true
},
{
"uuid":"d43f15b3-2208-43de-8fff-8d237c6918f9",
"sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"picked_up":true
},
{
"uuid":"472f832a-6f07-4af6-97ea-e6dc7b9b3799",
"sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"picked_up":true
}
],
"concrete_compressive_cylinder_200":[
{
"uuid":"d659d058-e4ec-4f72-9d73-9ea98295715a",
"sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"picked_up":true
},
{
"uuid":"777372e0-3e58-4292-bae4-bec84dfe1402",
"sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"picked_up":true
},
{
"uuid":"f63f7102-7673-4e71-97e5-2d85e0c1a93d",
"sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b",
"picked_up":true
}
]
}
},
{
"uuid":"61138cf3-0c49-4495-8a89-533c0a6e50bc",
"workorder_uuid":"4ad7bfe1-48d6-488c-bfaf-33f7189a41d7",
"org_sample_id":5002,
"sample_specimens":{
"concrete_compressive_cylinder_100":null,
"concrete_compressive_cylinder_200":null
}
}
]
}
I've gotten this far but it dosen't really work and now im more confused some guidance would be great
const out = res.data.concrete_samples.reduce((acc, sample) => {
const { sample_specimens } = sample;
const concrete_compressive_cylinder_100 = Object.keys(sample_specimens)["concrete_compressive_cylinder_100"];
const specimens = concrete_compressive_cylinder_100.map(obj => {
obj.picked_up ? console.log("picked up") : console.log("Not pickedn up")
});
}, []);

Array.prototype.reduce accepts a function whose return value is eventually returned from reduce itself. The function is passed each element of the array, along with the value accumulated so far. For example,
[1, 2, 3].reduce((accumulator, element) => accumulator + element)
// => 6
You can also provide an initial value, which will be passed to your function as accumulator on the first iteration.
At a basic level, to count how many occurrences of an object with a certain property with reduce, you could use something like this,
let array = [
{ foo: 4 },
{ foo: 6 },
{ bar: 8 },
]
array.reduce((count, element) => {
if (element.foo !== undefined) {
return count + 1
} else {
return count
}
}, 0)
// => 2
Extending this to your code (with extraneous data elided), with a nested reduce to get the count of cylinders with the desired picked_up property,
const data = {
"concrete_samples":[
{
"sample_specimens":{
"concrete_compressive_cylinder_100":[
{
"picked_up":true
},
{
"picked_up":true
},
{
"picked_up":true
}
],
"concrete_compressive_cylinder_200":[
{
"picked_up":true
},
{
"picked_up":true
},
{
"picked_up":true
}
]
}
},
{
"sample_specimens":{
"concrete_compressive_cylinder_100":null,
"concrete_compressive_cylinder_200":null
}
}
]
}
const result = data.concrete_samples.reduce((count, sample) => {
const cylinders = sample.sample_specimens.concrete_compressive_cylinder_100
if (cylinders == null) {
return count
}
const samplePickedUpCount = cylinders.reduce((pickedUpCount, cylinder) => {
if (cylinder.picked_up) {
return pickedUpCount + 1
} else {
return pickedUpCount
}
}, 0)
return count + samplePickedUpCount
}, 0)
console.log(result)
You could also use Array.prototype.filter to accomplish the same thing, getting an array of the cylinders with the desired property, and getting the length of that array.

If I understand correctly, you'd like to obtain a new concrete_samples array where the array values of nested sample_specimens objects are reduced to the number of items where picked_up is true - one approach to that would be as documented in the following snippet:
const data={"uuid":"4ad7bfe1-48d6-488c-bfaf-33f7189a41d7","org_workorder_id":1000,"concrete_samples":[{"uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","workorder_uuid":"4ad7bfe1-48d6-488c-bfaf-33f7189a41d7","org_sample_id":5001,"sample_specimens":{"concrete_compressive_cylinder_100":[{"uuid":"b9ef3a8a-2945-41e6-a34d-d90d1bd64819","sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","picked_up":true},{"uuid":"d43f15b3-2208-43de-8fff-8d237c6918f9","sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","picked_up":true},{"uuid":"472f832a-6f07-4af6-97ea-e6dc7b9b3799","sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","picked_up":true}],"concrete_compressive_cylinder_200":[{"uuid":"d659d058-e4ec-4f72-9d73-9ea98295715a","sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","picked_up":true},{"uuid":"777372e0-3e58-4292-bae4-bec84dfe1402","sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","picked_up":true},{"uuid":"f63f7102-7673-4e71-97e5-2d85e0c1a93d","sample_uuid":"776a8ccb-82fd-4a42-a6eb-8f286a4a9c0b","picked_up":true}]}},{"uuid":"61138cf3-0c49-4495-8a89-533c0a6e50bc","workorder_uuid":"4ad7bfe1-48d6-488c-bfaf-33f7189a41d7","org_sample_id":5002,"sample_specimens":{"concrete_compressive_cylinder_100":null,"concrete_compressive_cylinder_200":null}}]};
const concreteSamplesResult = data.concrete_samples.map(sample => {
// Iterate each key/value entry of sample_specimens, and reduce to a new
// specimens object that contains counts of picked_up: true items in sub array
const sample_specimens = Object
.entries(sample.sample_specimens)
.reduce((specimens, entry) => {
// Calculate count of picked_up items for arr of this entry
const [key, arr] = entry;
const count = Array.isArray(arr) ?
arr.reduce((total, item) => (total + (item.picked_up ? 1 : 0)), 0) : 0;
// Add count for entry key to newly reduced sample_specimen object
return { ...specimens, [key] : count };
},{})
return { ...sample, sample_specimens };
});
console.log(concreteSamplesResult);

Check null > loop > check null > loop and count isn't it?
function someFunc(json) {
const { concrete_samples } = json;
if (!concrete_samples) return;
let numberOFAvailableCylinder100s = 0;
const doSomethingWithCylinder = cylinder => {
console.log(cylinder.uuid);
numberOFAvailableCylinder100s += 1;
}
concrete_samples.forEach(concrete_sample => {
const { sample_specimens } = concrete_sample;
if (!sample_specimens) return;
findAvailableCylinder100(sample_specimens, doSomethingWithCylinder);
})
console.log(`count: ${numberOFAvailableCylinder100s}`);
}
function findAvailableCylinder100(sample_specimens, callback) {
const { concrete_compressive_cylinder_100 } = sample_specimens;
if (!concrete_compressive_cylinder_100) return;
concrete_compressive_cylinder_100.forEach(cylinder => {
if (!cylinder.picked_up) callback(cylinder);
});
}
someFunc(yourJSONObject);

Related

React native, multiple records getting pushed after 2nd object being pushed onto the array

React Native, Im trying to check the whether the object is present in the array or not, if present Im replacing the object with the new one, but after the 2nd object is inserted 3rd object is pushed twice, 4th object 4 times, Im not understanding why this behaviour is happening, is it because of immutable array or what?
useEffect(() => {
if (route.params && route.params.lookup_scan) {
showShipments();
}
}, [route]);
const showShipments = () => {
if (SegregationContext.shipments.length > 0) {
SegregationContext.shipments.forEach((item, key: number) => {
if (item.lookup_scan === route.params.lookup_scan) {
SegregationContext.shipments[key] = route.params;
} else {
SegregationContext.shipments.push({ ...route.params });
}
});
} else {
SegregationContext.shipments.push(route.params);
}
setShipments([...SegregationContext.shipments]);
setIsLoader(false);
};
what the solution was:
const index = shipments.findIndex(i => i.lookup_scan === newobj.lookup_scan); // Returns 2.
if (index === -1) {
shipments.push(newobj);
} else {
shipments[index] = newobj;
}
This is what I did but it didnt seem to work bcz it added the object once again after finishing the loop:
if (shipments.length > 0) {
shipments.map((item, key: number) => {
if (item.lookup_scan === newobj.lookup_scan) {
shipments[key] = newobj;
}
});
shipments.push(newobj);
} else {
shipments.push(newobj);
}
This is what I was doing:
if (shipments.length > 0) {
shipments.map((item, key: number) => {
if (item.lookup_scan === newobj.lookup_scan) {
shipments[key] = newobj;
} else {
shipments.push(newobj);
}
});
} else {
shipments.push(newobj);
}
actual code:
if (SegregationContext.shipments.length > 0) {
SegregationContext.shipments.forEach((item, key: number) => {
if (item.lookup_scan === route.params.lookup_scan) {
SegregationContext.shipments[key] = route.params;
}
});
SegregationContext.shipments.push({ ...route.params });
} else {
SegregationContext.shipments.push(route.params);
}
setShipments([...SegregationContext.shipments]);

Generic filter for X number of properties

I want to make a generic filter function. Currently I have a function that looks like this:
const filterRows = () => {
items.filter((item) => {
if(selectedDrinks.length > 0 && selectIds.length > 0) {
return selectedDrinks.includes(item.description) && selectedIds.includes(item.props.id)
}else if(selectedDrinks.length > 0) {
return selectedDrinks.includes(item.description)
}else if(selectedIds.length > 0) {
return selectedIds.includes(item.props.id)
}
}
}
The number of if checks I need to do will grow exponentially if I add one more thing to filter by.
I've made a pathetic try below. One issue I encountered is if I have a nested structure and want to access ["props/id"] as I don't know the syntax for it. Also tried ["props:id"] etc. And if I add multiple strings in the query it does not work either. And even if I could add multiple strings properly it would only work as an OR.
And for me it would be selectedDrinks && selectedId as both need to match for it to filter, not selectedDrinks || selectedIds
I want to include everything in both selectedDrinks and selectedIds as a query, and they should filter only if both are included in "assets" as description and props:id. I should also be able to add e.g "selectedNames" as a third "query parameter".
const selectedDrinks: string[] = [
"cola",
"fanta",
]
const selectedIds : string[] = [
"5",
"4",
]
interface s {
description: string;
name: string;
props: {
id: string
}
}
const items: s[] = [
{
description: "cola",
name: "computer",
props: {
id: "4"
}
},
{
description: "fanta",
name: "laptop",
props: {
id: "5"
}
},
{
description: "sprite",
name: "phone",
props: {
id: "6"
}
}
]
export function genericFilter<T>(
object: T,
filters: Array<keyof T>,
query: string[]
):boolean {
if(query.length === 0)
return true
return filters.some(filter => {
const value = object[filter]
console.log(value)
if(typeof value === "string") {
return value.toLowerCase().includes(query.map(q => q.toLowerCase()).join(""))
}
if(typeof value === "number") {
return value.toString().includes(query.map(q => q.toLowerCase()).join(""))
}
return false
})
}
const myFilterResult = items.filter((asset) => genericFilter(item, ["props", "name"], ["5"]))
console.log(myFilterResult)
If anyone is interested, here is how I solved it.
/**
*
* #returns A new list of filtered objects
* #param objects The objects that we want to filter
* #param properties The properties we want to apply on the object and compare with the query
* #param queries The queries we want to filter by
*/
export function genericFilter<T>(
objects: T[],
properties: Array<keyof T>,
queries: Array<string>[] | Array<number>[]
):T[] {
return objects.filter((object) => {
var count = 0;
properties.some((props) => {
const objectValue = object[props]
if(typeof objectValue === "string" || typeof objectValue === "number") {
queries.forEach((query) => {
query.forEach((queryValue) => {
if(queryValue === objectValue) {
count+=1;
}
})
})
}
})
return count === properties.length;
})
}
export default genericFilter;
How you call the function, can include X amount of filters and strings to search for.
const result = genericFilter(assets, ["description", "id", "name"], [selectedAssetTypes, selectedIds, selectedNames])

Push value of arrivalDate in array

I would like to store every arrivalDate in my array list.
Someone could tell me how can I do it?
But my array is still empty.
JSON returned by the API:
{
"reservations": {
"reservationInfo": [
{
"roomStay": {
"arrivalDate": "11am"
},
"WeatherR": {
"sound": "cloudy"
},
},
{
"roomStay": {
"arrivalDate": "7pm"
},
"WeatherR": {
"sound": "cloudy"
},
}
]
}
}
component.ts
searchForReservation() {
alert('hello');
this.http.get('/api/searchForReservation')
.subscribe((data) => {
this.ddataIno = data;
this.ddataIno = this.ddataIno.result.reservations.reservationInfo;
console.log('number of value', this.ddataIno.length);
console.log('content', this.ddataIno);
for (let i = 0; i <= this.ddataIno[i].length; i++) {
this.list = this.ddataIno.roomStay.arrivalDate;
}
console.log('store array', this.list)
})
}
searchForReservation() {
alert('hello');
this.http.get('/api/searchForReservation')
.subscribe((data) => {
const reservationInfo = this.data.result.reservations.reservationInfo;
this.list = reservationInfo.map(e => e.roomStay.arrivalDate);
})
}
Here's a working example in vanilla JS. You would need to make some small adjustments for angular, like this.list = ... instead of let list = ...
Using Array#map, you can create a new array from the JSON object
data.reservations.reservationInfo.map(r => r.roomStay.arrivalDate)
let data = {
"reservations": {
"reservationInfo": [{
"roomStay": {
"arrivalDate": "11am"
},
"WeatherR": {
"sound": "cloudy"
},
},
{
"roomStay": {
"arrivalDate": "7pm"
},
"WeatherR": {
"sound": "cloudy"
},
}
]
}
}
// declare your list as an array at the top
// list: []
// below would start off as 'this.list'
let list = data.reservations.reservationInfo.map(r => r.roomStay.arrivalDate);
console.log(list);
Your for loop is just reassigning the value of this.list
I suggest reading up on Array methods
I would use a map method, e.g.
this.list = this.ddataIno.result.reservations.reservationInfo.map(i => i.roomStay.arrivaldate);

Get request using loop

There was such a problem.
I'm trying to make a certain number of GET requests in Wikipedia API using a cycle.
Trying to do this with the function getAllInfo()
articles.components.ts
export class ArticlesComponent {
constructor(private articlesServices: ArticlesService) { }
#Input() allTitles: string[];
articlesInfo: ArticleInformationNew;
allArray: [[string, number]] = [['', 0]];
static getUrlInformation(searchQuery: string) {
return 'https://ru.wikipedia.org/w/api.php?action=query&titles='
+ searchQuery + '&prop=info&format=json&origin=*';
}
getAllInfo() {
for (const title of this.allTitles) {
this.articlesServices.getArticleInformation(ArticlesComponent.getUrlInformation(title))
.subscribe(
(data: ArticleInformation) => this.articlesInfo = {
...data,
query: { pages: [Object.values(data.query.pages)[0]]}}
);
this.allArray.push([this.articlesInfo.query.pages[0].touched, this.articlesInfo.query.pages[0].length]);
}
}
}
articles.service.ts
export interface ArticleInformation {
batchComplete: string;
query: {
pages: {
}
};
}
export interface ArticleInformationNew {
batchComplete: string;
query: {
pages: any[]
};
}
export class ArticlesService {
constructor(private http: HttpClient) { }
getArticleInformation(url) {
return this.http.get<ArticleInformation>(url);
}
}
An array this.allTitles can consist of a number of lines. For example: this.allTitles = ['Naumen', 'Naumen DMS']
I expect that the arraythis.allArray will be two-dimensional and contain arrays that consist of rows with data for each query. For example:
this.allArray[0] = ['', 0]
this.allArray[1] = ['2019-02-01T23:27:26Z', 3687]
this.allArray[2] = ['2019-01-21T04:24:21Z', 9704]
But in fact, it turns out that each element of a two-dimensional array is the same. For example:
this.allArray[0] = ['', 0]
this.allArray[1] = ['2019-02-01T23:27:26Z', 3687]
this.allArray[2] = ['2019-02-01T23:27:26Z', 3687]
Why and how to fix it?
Try this,
getAllInfo() {
for (const title of this.allTitles) {
this.articlesServices.getArticleInformation(ArticlesComponent.getUrlInformation(title))
.subscribe(
(data: ArticleInformation) => {
this.articlesInfo = {
...data,
query: { pages: [Object.values(data.query.pages)[0]]}
}
this.allArray.push([this.articlesInfo.query.pages[0].touched,this.articlesInfo.query.pages[0].length]);
}
);
}
}
You can use combineLatest: https://www.learnrxjs.io/operators/combination/combinelatest.html
First, collect observables to be combined (but without subscribing to them), then combine them with combineLatest and get the response as an array and iterate over it.
getAllInfo() {
console.log(this.allTitles);
observablesToSubscribe = [];
for (const title of this.allTitles) {
observablesToSubscribe.push(this.articlesServices.getArticleInformation(ArticlesComponent.getUrlInformation(title)));
}
combineLatest(observablesToSubscribe).subscribe((responseData: Array<ArticleInformation>) => {
responseData.forEach((responseDatum) => {
this.allArray.push({
...data,
query: { pages: [Object.values(data.query.pages)[0]]}
})
});
});
}
Maybe, I misunderstood the question, but you can get pages with particular titles, by correcting your searchQuery (using alternative separators for titles), and get rid of for loop:
getAllInfo() {
console.log(this.allTitles);
this.articlesServices.getArticleInformation(
ArticlesComponent.getUrlInformation(this.allTitles.join('|'))
.subscribe(
(res => {
// here is a result with all pages of particular titles,
// then you can process your result...
console.log(res);
// this.allArray is a multidimensional array
// [["2019-01-25T00:45:06Z",4508],
// ["2019-01-26T07:25:08Z", 773]]
this.allArray = Object.keys(res.query.pages)
.reduce((acc, val, index) => {
acc[index] = [pages[val].touched, pages[val].length];
return acc;
}, []);
});
}
searchQuery for titles will be in that case Naumen | Naumen DMS (not, for example, just Naumen). | (pipe) is an alternative separator for titles.
To process the result (res.query.pages):
const pages = {
"755288": {
"pageid": 755288,
"ns": 0,
"title": "Spring",
"contentmodel": "wikitext",
"pagelanguage": "ru",
"pagelanguagehtmlcode": "ru",
"pagelanguagedir": "ltr",
"touched": "2019-01-26T07:25:08Z",
"lastrevid": 84434967,
"length": 773
},
"92586": {
"pageid": 92586,
"ns": 0,
"title": "Atom",
"contentmodel": "wikitext",
"pagelanguage": "ru",
"pagelanguagehtmlcode": "ru",
"pagelanguagedir": "ltr",
"touched": "2019-01-25T00:45:06Z",
"lastrevid": 95248014,
"length": 4508
},
};
const arr = Object.keys(pages).reduce((acc, val, index) => {
acc[index] = [pages[val].touched, pages[val].length];
return acc;
}, []);
console.log(arr);

typescript Promise - persist change in mapped array of objects

I am trying to replace the value (file paths) of the key/value entries in an array of objects upon the if-condition, that a file/ or files exist in the file directory Documents ( ios capacitor ionic ); else, just return the array unchanged.
Arrays
const currentItems = this.data;
const filenames = [val, val, ...];
// for loop
for (let filename of filenames) {
// capacitor FileSystem API; promise
Plugins.Filesystem.stat({
path:filename+'.jpeg',
directory: FilesystemDirectory.Documents
}).then((result) => {
// return path to file in Documents directory ( simplified)
const result.uri = this.imagepath;
// map array
const newItems = this.currentItems.map(e => {
// if entries match set the value for key 'linethree'
if (e.lineone === filename) {
return {
...e,
linethree: this.imagepath
}
}
// else, return e unchanged
else
return { ...e,}
});
}).catch( reason => {
console.error( 'onRejected : ' + reason );
})
}
The problem:
on every iteration - filename of filenames - the original array is mapped again - with its original values; thus each iteration overwrites the change from the previous iteration.
How can it be achieved that the value entry at key 'linethree' for each match - e.lineone === filename - persists ?
Desired replacement:
const filenames = ["uncle"];
[{"lineone":"nagybácsi","linetwo":"uncle","linethree":"./assets/imgs/logo.png"}]
[{"lineone":"nagybácsi","linetwo":"uncle","linethree":"_capacitor_/var/mobile/Containers/Data/Application/D95D4DEF-A933-43F1-8507-4258475E1414/Documents/nagybácsi.jpeg"}]
If i understand well you need something like this:
Solution with Array#Filter, Array#Some and Array#Map
const wantedImagePath = '_capacitor_/var/mobile/Containers/Data/Application/D95D4DEF-A933-43F1-8507-4258475E1414/Documents/nagybácsi.jpeg';
const fileNames = ["uncle"];
const someData = [
{
"lineone":"ikertestvérek; ikrek",
"linetwo":"twins",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nagybácsi",
"linetwo":"uncle",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nőtlen (man)",
"linetwo":"unmarried",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone": "bar",
"linetwo": "foo",
"linethree": "./some/demo/path/logo.png"
}
];
const modifed = someData.filter(x => fileNames.some(y => y === x.linetwo)).map(z => ({ ...z, linethree: wantedImagePath }));
console.log(modifed)
Update:
Solution if you want to keep current data and modify matched:
const wantedImagePath = '_capacitor_/var/mobile/Containers/Data/Application/D95D4DEF-A933-43F1-8507-4258475E1414/Documents/nagybácsi.jpeg';
const fileNames = ["uncle"];
const someData = [
{
"lineone":"ikertestvérek; ikrek",
"linetwo":"twins",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nagybácsi",
"linetwo":"uncle",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nőtlen (man)",
"linetwo":"unmarried",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone": "bar",
"linetwo": "foo",
"linethree": "./some/demo/path/logo.png"
}
];
const modified = someData.map(x => {
let match = fileNames.find(y => x.linetwo === y);
return match !== undefined ? ({ ...x, linethree: wantedImagePath }) : x;
});
console.log(modified)

Resources