How to normalize the data from the JSON with normalizr? - reactjs

I am quite new to the normalizr and can't understand it well enough yet. How can I normalize the following JSON response so it can be used for the Redux:
{
"statusCode":200,
"message":"Random quotes",
"pagination":{
"currentPage":1,
"nextPage":null,
"totalPages":1
},
"totalQuotes":1,
"data":[
{
"_id":"5eb17aadb69dc744b4e70e05",
"quoteText":"One crowded hour of glorious life is worth an age without a name.",
"quoteAuthor":"Walter Scott",
"quoteGenre":"age",
"__v":0
}
]
}
It would be useful to put the data object at the top level in the normalized object.
How can I combine this with TypeScript?
Thank you in advance.

Typescript types can definitely help you to understand the data that you are dealing with. You want to describe various pieces of the response with their own types and then piece them together.
interface Quote {
_id: string;
quoteText: string;
quoteAuthor: string;
quoteGenre: string;
__v: number;
}
interface Pagination {
currentPage: number;
nextPage: null | number; // what is this when it's not null?
totalPages: number;
}
interface APIResponse {
statusCode: number;
message: string;
pagination: Pagination;
totalQuotes: number;
data: Quote[];
}
normalizr isn't super helpful here because you only have one entity type which is a Quote. In a sense you have two entity types if you are treating the response itself as an entity. But I'm not sure how you would extract a unique id from it. You'd probably have to add it yourself based on the API path/params since that information is lacking in the JSON.
const quote = new schema.Entity("quote", {}, { idAttribute: "_id" });
const response = new schema.Entity("response", {
data: [quote] // an array of quote entities
});
console.log(normalize({...json, id: "/random-quote"}, response));
This gives you
{
"entities": {
"quote": {
"5eb17aadb69dc744b4e70e05": {
"_id": "5eb17aadb69dc744b4e70e05",
"quoteText": "One crowded hour of glorious life is worth an age without a name.",
"quoteAuthor": "Walter Scott",
"quoteGenre": "age",
"__v": 0
}
},
"response": {
"/random-quote": {
"statusCode": 200,
"message": "Random quotes",
"pagination": {
"currentPage": 1,
"nextPage": null,
"totalPages": 1
},
"totalQuotes": 1,
"data": ["5eb17aadb69dc744b4e70e05"],
"id": "/random-quote"
}
}
},
"result": "/random-quote"
}

Related

MongoDb- get length of array in updateMany (Node.js)

I am trying to do an update many.
The problem that I am encountering is getting the length of an array and seeing if it is less than or equal to another number being passed in from the params.
This is what I have done so far. But I feel this is a bad way of doing it. if anyone has a better way of doing it, please give your input. trying to improve :)
const updateStatus = (materialId, attempts) => {
return db.collection.updateMany(
{
_id,
[`data.${attempts}`]: { $exists: true },
status: { $in: ['outstanding', 'overdue'] },
},
{ status: 'restricted' }
)
}
this is the data I am trying to update many on
[{
_id: 1,
status: 'outstanding',
data:[],
},
{
_id: 2,
status: 'overdue',
data:[{passed: true},{passed: false}],
}
{
_id: 3,
status: 'outstanding',
data:[{passed: true},{passed: false}, {passed: false}],
}
]

Get chats list MongoDB

In my chat app (+20m users for example) i need to get last updated chats but there's no optimal solution.
Chats
{
id: 1,
title: 'My Group',
updated_at: 137974654
},
{
id: 2,
title: 'Gamers',
updated_at: 137973654
}
Members
{
chat_id: 1,
user_id: 'A'
},
{
chat_id: 2,
user_id: 'B'
}
I don't want to embed members because a user can join thousands of chats so document size is beyond 16MB.
The maximum BSON document size is 16 megabytes.
A potential problem with the embedded document pattern is that it can lead to large documents, especially if the embedded field is unbounded. In this case, you can use the subset pattern to only access data which is required by the application, instead of the entire set of embedded data.
https://docs.mongodb.com/manual/tutorial/model-embedded-one-to-many-relationships-between-documents/#subset-pattern
My solution but not efficient
I first tried to get a list of chat_ids then query chats:
const members = Members.find({ user_id: 'A' }).project({ chat_id: 1 });
const chat_ids = members.map(item => item.chat_id);
const chats = Chats.find({
id: {
$in: chat_ids
}
}).sort({ updated_at: -1 }).limit(20);
But as i said, What if a user has joined +100000 chats? so the first query is too large + the last query is too slow.
What should i do? Do i need a relational database?
Making some assumptions that you are only interested in user_id: 'A', and keeping in mind that I am a MongoDB noob, and I only tested this with the example data you provided, does this do what you want?
db.members.aggregate([
{
$match: {
user_id: "A"
}
},
{
$lookup: {
from: "chats",
localField: "chat_id",
foreignField: "id",
as: "thechats"
}
},
{
"$unwind": "$thechats"
},
{
"$replaceRoot": {
"newRoot": "$thechats"
}
},
{
"$sort": {
updated_at: -1
}
},
{
"$limit": 20
}
])
Try it at mongoplayground.net.

How to get object in deeply nested array in mongoose using nodes

In my collection of users I have the following
{
_id: ObjectId('whatever user id'),
movies: [
{
_id: ObjectId('whatever id of this movie'),
name: 'name of this movie',
actors: [
{
_id: ObjectId('whatever id of this actor'),
name: 'name of this actor'
}
]
}
]
}
So in my users collection I want to be able to query for a actor by the user.id, pet.id, and the actor.id
I want to return the actor somewhat like this...
actor: {
fields...
}
I tried the following...
const actor = await User.findById(req.user.id, {
movies: {
$elemMatch: {
_id: req.params.movie_id,
actors: {
$elemMatch: {
_id: req.params.actor_id,
},
},
},
},
});
I have tried other things but can't seem to get it to work. I saw that you can maybe use aggregate but I am not sure how to query that while using the ids I have at my disposal.
I was able to figure it out by using aggregate. I was using this before but it seems that I needed to cast my ids with mongoose.Types.ObjectId so a simple req.user.id would not work.
In order to get my answer I did...
const user = await User.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(req.user.id) } },
{ $unwind: '$movies' },
{ $match: { 'movies._id': mongoose.Types.ObjectId(req.params.movie_id) } },
{ $unwind: '$movies.actors' },
{
$match: {
'movies.actors._id': mongoose.Types.ObjectId(req.params.actor_id),
},
},
]);
This did not return data in the following format...
actor: {
fields...
}
but returns it instead like this...
user: {
movies: {
actor: {
fields...
}
},
otherFields...
}
then sending the response back...
res.status(200).json({
status: 'success',
data: {
actor
}
})
gives that format I wanted. However, I would still want to know how to just get the data actor without getting the full document

How to return id with Mongoose Aggregate Lookup with Apollo GraphQL

I have an aggregate function that returns people in a collection:
const getById = ({ id }) => {
return Project.aggregate([
{ $match: { _id: Types.ObjectId(id) } },
{
$lookup: {
from: "members",
localField: "_id",
foreignField: "project_id",
as: "members"
}
},
])
.then(data => {
const [project] = data;
console.log(project) // see below
return {
id: project._id,
...project
};
})
.catch(err => console.log(err));
}
If I return the data from this I get the following:
// Server response
{ _id: 5e2f57b577a8ce59c79e74af,
title: 'ok',
user_id: 5e2dc7961e6b840c315b5a03,
__v: 0,
members:
[ { _id: 5e447683b4f732cc9c4a9531,
name: 'Karl Taylor',
email: 'karl#queuey.dev',
project_id: 5e2f57b577a8ce59c79e74af,
position: 1,
__v: 0 },
{ _id: 5e45be128ed96a5eaef5d13e,
name: 'John Smith',
email: 'john#queuey.dev',
project_id: 5e2f57b577a8ce59c79e74af,
position: 2,
__v: 0 } ] }
However, when I query from the frontend using Apollo GraphQL, the id is null. (But it works on other items, as id is a getter for id but this does not happen on aggregate functions).
What is the best practice to map the id to the correct value? I would normally just use array.map but I feel like that might be overkill if I have too many members (at which point I would probably break this out to do pagination, but that's a different story.)
This is the response from frontend
// Client response
"project": {
"id": "5e2f57b577a8ce59c79e74af",
"title": "ok",
"members": [
{
"id": null, // <-- Notice here is null
"name": "Karl Taylor",
"email": "karl#queuey.dev",
"__typename": "Member"
},
{
"id": null, // <-- Notice here is null
"name": "John Smith",
"email": "john#queuey.dev",
"__typename": "Member"
}
],
"__typename": "Project"
}
This question here is similar, however, I do not believe it is a duplicate because we are querying different data. (the answer does not solve my question.)
I need to be able to return id otherwise cached redirects will not work.
I ran into this problem as well when I try to use aggregations.
simply you can you both ID's(id and _id) but it's not good thing.
what I use is to iterate the data
if the returning data of the query is not array use this
return {
...res._doc,
id: res._id,
}
but the returning data is an array you can add id to the response variable like below using forEach
res.forEach(element => {
element.id = element._id
});
return res;

React Axios Get Call to Output JSON Format

I am performing an Axios get call in a React Component to retrieve JSON info. That function is working great. Within the JSON is a label for various network ports, which are returning as an array in my axios call. These are ultimately going to be displayed as nodes on a d3 graph. My issue is that I need to output the data pulled from the get call into the following format:
nodes: [
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' }
]
So the full component for the graph to read is:
export const data = {
nodes: [
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' },
{ id: 'JSON data.label here' }
]
}
Here is the format of the Axios get I am using:
axios.get(`NetworkConstruct.json`)
.then(res => {
const names = res.data.items;
this.setState({ names });
});
Here is a sample output I am receiving (there are 11 of these):
{id: "5bc0860c-ece1-461c-bac0-b155a3cacd82", label: "80.107.0.212",
resourceTypeId: "tosca.resourceTypes.NetworkConstruct", productId:
"5bc0835c-6cfa-486e-8429-a59eaf4118bc", tenantId: "393fa8da-61fd-458c-80f9-
ce92d0ef0330", …}
The data has to be in this EXACT format or the graph won't read it. I'm guessing I'll need to do an initial map function but am stuck on how to arrange it. I cannot have any divs or quotes in my output. Is this doable? I have scoured the boards and Google for a couple of days and can't make this work yet.
Here is the object I am receiving from the GET request.
{
"id": "5bd2c6ef-6009-4b90-9156-62168f3c6293",
"resourceId": "5bd0ba82-2994-455d-8716-2adb5694d6f0",
"interface": "getGraph",
"inputs": {},
"outputs": {
"graph": {
"nodes": [
{
"id": "5bcdf06c-dd53-4335-840f-55a4b8d85a2d",
"name": "asw-lab9306b",
"ports": {
"GigabitEthernet3/0/8": "5bd1777f-0ab9-4552-962b-9e306ce378ab",
"GigabitEthernet2/0/15": "5bd1777e-119c-44e8-ba69-0d86a481c0f5",
"GigabitEthernet3/0/47": "5bd17783-be94-4aaf-8858-70e4eb3d02dc",
"GigabitEthernet2/0/13": "5bd17783-ed99-453f-a958-f764edaa8da8"
}
}
],
"links": [
{
"a": "5bd1a467-13f2-4294-a768-561187b278a8",
"z": "5bd17770-2e6c-4c37-93c8-44e3eb3db6dd",
"layer": "ETHERNET"
},
{
"a": "5bd1776e-c110-4086-87d6-a374ccee419a",
"z": "5bd17770-83ee-4e10-b5bb-19814f9f5dad",
"layer": "ETHERNET"
}
]
}
},
"state": "successful",
"reason": "",
"progress": [],
"providerData": {},
"createdAt": "2018-10-26T07:49:03.484Z",
"updatedAt": "2018-10-26T07:49:25.425Z",
"resourceStateConstraints": {},
"executionGroup": "lifecycle"
}
The info I need is the nodes ID. There are eleven of them in the full object.
You can map an array of objects to another array of objects in your format with Array.prototype.map(). Assuming that data is the list of objects from your response:
class Graph extends React.Component {
state = {
nodes: null,
};
componentDidMount() {
axios.get('the url').then(response => {
const nodes = response.data.outputs.graph.nodes;
this.setState({nodes});
});
}
render() {
const {nodes} = this.state;
if (!nodes) return 'Loading...'
return <TheD3ComponentYouUse nodes={nodes} />;
}
}

Resources