I'm using Firebase in my Angular2 app to retrieve data from my database. What's the easiest was of returning an array of unique values in the nested tags list? I.e. in my example I want to return ["Sport", "Adventure", "Music"] where the 2nd "Adventure" is omitted.
{
"images": {
"image1": {
"path": "path",
"date": 43534532123,
"tags": {
0: "Sport",
1: "Adventure"
}
},
"image2": {
"path": "path",
"date": 43534532123,
"tags": {
0: "Music",
1: "Adventure"
}
}
}
I tried this approach but it seems to split the elements from the first image only
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags))
.concatAll()
.distinct()
However, this approach yields the correct output but as a separate stream of the unique tags instead of as one array
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags))
.mergeAll()
.mergeAll()
.distinct()
UPDATE
I assumed in the original answer that it was a stream with the individual items. Later the OP clarified it is a stream of a list of photos. In this case we use Array#reduce instead of Observable#reduce.
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags)
.reduce((allTags, current) => {
return allTags.concat(
current.filter(item => allTags.indexOf(item) === -1))
}, []))
ORIGINAL ANSWER
distinct over an observable returns unique individual values in an stream, but is not the operator we want here. It's is possible to produce a sequence with all the tags, each one as a separate value, but then we would need to regroup them again.
We can use instead reduce, quite similar to the Array counterpart. It takes an initial value ([]) and accumulates the others. We built a list with the individual values on each iteration. After the reduce we have an array of unique tags.
Notice that .list() should complete the observable for it to work.
return this.af.database.list('/photos/user')
.map(photos => photos.map(photo => photo.tags))
.reduce((allTags, current) => {
return allTags.concat(
current.filter(item => allTags.indexOf(item) === -1))
}, [])
Related
How to filter out nested children and groups, so in the below example if i searched for "Nest Name 3" then only Group B would be returned and only Nested Name 3.
I have an arrary like this:
items = [
{
"key": "Group A",
"value": [
{
"name": "Nest Name 1"
}
},
{
"key": "Group B",
"value": [
{
"name": "Nest Name 2"
},
{
"name": "Nest Name 3"
}
[
}
]
And in an angular pipe i am trying to return (in the same format) groups where the nested name matches a string. So in this case if i searched for 2 then only Group b should be brought back and only the first nested object would be brought back.
I have my match working on none nested code:
items?.filter(item => searchText.split(' ').every(q => new RegExp(q, 'i').test(item[field]))
searchText is the word i am searching for and field is the name of the field in this case name.
I thought something like this would work:
var tempList = [];
items.filter(group => group.value.filter(item => searchText.split(' ').every(q => new RegExp(q, 'i').test(item[field])))).forEach(product => tempList.push(product));
return tempList;
but while it brings back the correct groups the actual nested items are not removed.
Thanks
To search through your items, you need to compare the name of each item.value against your searchText string also inside each group.value. Here's the code to do so:
const filterItems = (searchText: string): Group[] => {
return items
.filter((item) =>
item.value.some((value) => isSearchValue(value, searchText))
)
.map((group) => {
return {
key: group.key,
value: group.value.filter((value) =>
isSearchValue(value, searchText)
),
};
});
};
The some() method was used to iterate through the array of values.
This method returns true if at least one element in the array
satisfies the provided testing function.
Documentation for the some method
The function isSearchValue can be like this:
const isSearchValue = (value:ItemValue,searchText:string):boolean=>{
return value.name.toLowerCase().includes(searchText.toLowerCase());
}
Where there are the following types and interfaces:
type ItemValue = {name:string};
interface Group{
key:string;
value:ItemValue[];
}
To give you a better idea of what's happening, I've created a StackBlitz example with an input search feature.
This question already has an answer here:
Filter array of objects where one object's array has at least one match with another array
(1 answer)
Closed 2 months ago.
Essentially, I am trying to filter out specific indices that are not in criteria array. Data that I am getting from JSOn file looks like this:
let data = [
{
"category": ["business"],
"title": "some title"
},
{
"category": ["travel", "business"],
"title": "some title"
},
{
"category": ["sport"],
"title": "some title"
},
{
"category": ["business", "sport"],
"title": "some title"
}
]
The array that should filter category of each object from the data above:
var criteria = ["business", "travel"]
I've tried numerous options, the ones that I've listed below are only the ones that either return something or not bring any errors, however, both methods do not bring desired outcome.
const filtered = data.filter((item) =>
// this returns only first "business" category while ignoring the rest
criteria.find(element => item.category == element)
)
const filtered = data.filter((item) =>
// this simply returns all data entries, even the ones that are not in criteria array.
criteria.filter(element => item.category == element)
)
const filtered = spanishData.filter(function (item) {
// returns all data entries as well
return criteria.indexOf(item.category) === -1
})
How can I filter the data > category array according to the criteria array?
Thank you
You can use Array#filter along with Array#some.
let data=[{category:["business"],title:"some title"},{category:["travel","business"],title:"some title"},{category:["sport"],title:"some title"},{category:["business","sport"],title:"some title"}],
criteria=["business","travel"];
let res = data.filter(x => x.category.some(c => criteria.includes(c)));
console.log(res);
Been delivered some confusing JSON data with a problem I haven't seen before.
The JSON is formatted similar to this structure:
[
{
"title": "Event",
"start_date": "2022-08-20 15:00:00",
"end_date": "2022-08-20 16:00:00",
"branch": {
"85": "branchname"
},
"room": {
"156": "roomname"
},
"age_group": {
"5": "Youth",
"6": "Teen"
}
},
{
"title": "Event02",
"start_date": "2022-08-20 15:00:00",
"end_date": "2022-08-20 16:00:00",
"branch": {
"72": "branchname"
},
"room": {
"104": "roomname02"
},
"age_group": {
"5": "Youth",
"6": "Teen"
}
}
]
I'm trying to pull roomname out of the data, but it's nested in an object that has a random index number. If I manually put in the index number, I can retrieve the data, but the number changes every entry.
If I can figure out how to retrieve the number and store it in a variable, then use it again, or just somehow wildcard to just show any child of any key under the parent node "room" it would work perfect, but I don't know of a way to do this in javascript.
I'm limited to vanilla javascript, no external libraries or jquery.
here is the code that will output correctly if I manually enter the index numbers, but it only works for a single entry.
<script>
const url = 'example.json';
fetch(url)
.then((response) => {
return response.json();
})
.then((json) => {
json.map(function(event) {
console.log(`${event.start_date}`);
console.log(`${event.title}`);
console.log(`${event.room[156]}`);
return element;
});
}, 80);
</script>
EDIT: Forgot to point out, there is always only 1 entry in the "room" tag, but it's index is randomized, so if you just select the room tag it returns undefined or invalid. If I could wildcard the index so it just tries them all, or somehow retrieve the index number and store it in a variable, it would fix the issue.
I think this will work:
Here as you don't know the key so, instead of just guessing, you can use Object.values(JSONObjName) to get the list/array of values in that json.
Here I'm also using optional chaining (?.) to handle the case when the json has no key value pairs.
<script>
const url = 'example.json';
fetch(url)
.then((response) => {
return response.json();
})
.then((json) => {
json.map(function(event) {
const roomName = Object.values(event.room)?.[0];
console.log(`${event.start_date}`);
console.log(`${event.title}`);
console.log(`${roomName}`);
return {...event, room: roomName};
});
}, 80);
</script>
As long as you always want the first key you can fetch it like this
room = event.room[Object.keys(event.room)[0]]
if you want to get just roomname, you could do Object.values(room)[0]
or if you want the index and value you could go for Object.entries(room)[0]
arr?.map(({ room }) => {
for(let [key, value] of Object.entries(room)) {
console.log('Random Key : ',key)
console.log('Roomname : ', value)
console.log('Using random key : ',room[key])
}
})
By this way you can find the value of room against the random key.
Or you can try this if it is more relevant to you.
arr.map(({ room }) => {
for(let key of Object.keys(room)) {
console.log('Random Key : ',key)
console.log('Using random key : ',room[key])
}
})
Since you may want to do this for branch as well, here's an alternative solution which uses the object key as a computed property name (aka "dynamic key") to get the value.
And since, in this example it's done more than once, I've added that to a function that you can call in the destructuring assignment.
const data=[{title:"Event",start_date:"2022-08-20 15:00:00",end_date:"2022-08-20 16:00:00",branch:{85:"branchname"},room:{156:"roomname"},age_group:{5:"Youth",6:"Teen"}},{title:"Event02",start_date:"2022-08-20 15:00:00",end_date:"2022-08-20 16:00:00",branch:{72:"branchname02"},room:{104:"roomname02"},age_group:{5:"Youth",6:"Teen"}}];
// Get first key from an object
function getKey(obj) {
return Object.keys(obj)[0];
}
const out = data.map(obj => {
// Destructure the object and call `getKey` with the
// object to get its only key, and use that
// as a computed property to get its value, which
// we then relabel e.g. `roomName`
const {
branch: { [getKey(obj.branch)]: branchName },
room: { [getKey(obj.room)]: roomName },
...rest
} = obj;
// Now just return a new object with your new keys/values
return { ...rest, branchName, roomName };
});
console.log(out);
Additional documentation
Rest parameters
Spread syntax
I'm trying to fetch data from my backend, creating a class object for each item I get
getRankingList(type: RankingListType, page: number) {
let params = new HttpParams().set("pid", String(page)).set("limit", String(5));
return this.http.get(`http://127.0.0.1:3333/ranking/player/all`, { params })
.pipe(
map(item => new RankingGuild(item['guild'], item['name'], item['country'], item['honor'], item['RawKey']))
);
}
The data I'm receiving from the backend looks like this:
[
{
"RawKey": "1",
"honor": 0,
"guild": "Test",
"name": "test",
"country": 1
},
{
"RawKey": "2",
"honor": 0,
"guild": "Test2",
"name": "test2",
"country": 1
}
]
But instead of iterating through the object, "item" is the object itself, meaning there is only one iteration returning the object that I had in the first place, rather than its entries. I've been searching for hours to find a solution, but it seems like this is the correct way to do it, not sure why it doesn't work.
This is because the RxJS map operator and JavaScript's Array.map() are 2 different things altogether. You should read up on their differences.
In short, the RxJS map operator allows you to apply a given project function to each value emitted by the source Observable, and emit the resulting values as an Observable. On the other hand, the Array.map() method merely creates a new array with the results of calling a provided function on every element in the calling array.
If you want to map the value returned by the response from the HTTP request, I believe this is what you should be doing instead.
getRankingList(type: RankingListType, page: number) {
let params = new HttpParams().set("pid", String(page)).set("limit", String(5));
return this.http.get(`http://127.0.0.1:3333/ranking/player/all`, { params })
.pipe(
map(response => response.map(item => new RankingGuild(item['guild'], item['name'], item['country'], item['honor'], item['RawKey'])))
);
}
Then, on your component itself, you may subscribe to the method to return the actual values itself.
getRankingList.subscribe(res => {
// do the rest
})
The rx map operator is not the array map operator. The array map transforms items in an array, the rx map transforms items in a stream, and the item in the stream in this case is an array. Do this:
return this.http.get(`http://127.0.0.1:3333/ranking/player/all`, { params })
.pipe(
map(items => items.map(item => new RankingGuild(item['guild'], item['name'], item['country'], item['honor'], item['RawKey'])))
);
Use the array map inside your rx map.
I have an REST API interface which only gets me the first level of some information.
So for example I want to collect groups. Every Group can have subgroups. So for example "Group 1" has the Subgroups "Group A" and "Group B". "Group A" has the Subgroup "GroupX". And so on.
But the API only gives me the first level of Groups for a group name. So I pass "Group 1" to the API and it returns "Group A" and "Group B". To get the supgroups of Group A, I need to call the API again. But i don't know how many iterations of this it will have.
So I thought about using recursion but I haven't come far.
So far my Code:
getGroupChildren(group:string){ return this restService.getGroupChildren(group)}
getGroups():Promise<any>{
let collection:string[] = [];
return this.getGroupChildren("Group A").then((result)=> {
if(result.data.length !==0){
return this.getGroupChildren(data[0].groupName);
}
});
}
Now this will only return me the first Supgroups of the first element.
How can I accomplish it will always find every Supgroup no matter how many? Maybe is it good to use Observables?
Here an example structure of one API call:
{ "groupName" : "Group_1", "children" : ["Group_A", "Group_B"]}
You can achieve what you want with flatMap operator of Observable
getGroups(group: string) {
return this.http.get(`/group/{group}`).flatMap(response => {
if (response.children.length === 0) { // you hit a leaf, stop recursion here
return Observable.of(response);
} else { // there are more levels to go deeper
return this.getGroups(response.children[0].groupName);
}
});
}
Edit Using Promise
Let's say you use a GroupService which returns the data instead of HttpClient. You can convert a Promise to an Observable with fromPromise operator.
getGroups(group: string) {
return Observable.fromPromise(this.groupService.get(group)).flatMap(response => {
if (response.children.length === 0) { // you hit a leaf, stop recursion here
return Observable.of(response);
} else { // there are more levels to go deeper
return this.getGroups(response.children[0].groupName);
}
});
}
Edit 2 Using this service
Let's take a look at your example. You have following json
{
"groupName": "Group_1",
"children" : ["Group_A", "Group_B"]
}
In your component file, you call the service as follows
...
this.recursiveGroupService.getGroups("Group_1")
.subscribe(response => {
// at this point response will be `Group_A`
})
Edit 3 Getting the whole object
This time we'll use forkJoin and call getGroups for all of the children and collect the results in a children array.
Note: I haven't tested this code myself. It may contains some error. If it has, let me know.
import { forkJoin, of } from 'rxjs';
import { map } from 'rxjs/operators';
getGroups(group: string) {
let retVal;
return Observable.fromPromise(this.groupService.get(group)).flatMap(response => {
retVal = {
groupName: response.groupName
};
if (response.children.length === 0) { // you hit a leaf, stop recursion here
return of(retVal);
} else { // there are more levels to go deeper
// this will create list of observable for each child
const children$ = response.children.map(
child => this.getGroups(child));
// forkJoin will execute these observables in parallel
return forkJoin(children$).pipe(
map(results => {
// results is an array containing children data
retVal.children = results;
return retVal;
})
);
}
});
}
You could use Promise.all to recursively resolve deeper children, and then take the result (an array) to create an object to resolve the promise with:
getGroups(groupName = "Group A") {
return this.getGroupChildren(groupName).then((result) =>
Promise.all(result.data.map( ({groupName}) => this.getGroups(groupName) ))
).then(children => ({ groupName, children }));
}
So the promised value could be something like:
[{
groupName: "Group A",
children: [{
groupName: "Group A1",
children: []
}, {
groupName: "Group A2",
children: []
}]
}]