EDIT: Re-structured question, cleaer, and cleaner:
I have a data object from Sequelize that is sent by node-express:
{
"page": 0,
"limit": 10,
"total": 4,
"data": [
{
"id": 1,
"title": "movies",
"isActive": true,
"createdAt": "2020-05-30T19:26:04.000Z",
"updatedAt": "2020-05-30T19:26:04.000Z",
"questions": [
{
"questionsCount": 4
}
]
}
]
}
The BIG question is, how do I get the value of questionsCount?
The PROBLEM is, I just can't extract it, these two methods give me undefined result:
category.questions[0].questionsCount
category.questions[0]['questionsCount']
I WAS ABLE to get it using toJSON() (From Sequelize lib I think), like so:
category.questions[0].toJSON().questionsCount
But I'd like to know the answer to the question, or at least a clear explanation of why do I have to use toJSON() just to get the questionsCount?
More context:
I have this GET in my controller:
exports.getCategories = (req, res) => {
const page = myUtil.parser.tryParseInt(req.query.page, 0)
const limit = myUtil.parser.tryParseInt(req.query.limit, 10)
db.Category.findAndCountAll({
where: {},
include: [
{
model: db.Question,
as: "questions",
attributes: [[db.Sequelize.fn('COUNT', 'id'), 'questionsCount']]
}
],
offset: limit * page,
limit: limit,
order: [["id", "ASC"]],
})
.then(data => {
data.rows.forEach(function(category) {
console.log("------ May 31 ----> " + JSON.stringify(category.questions[0]) + " -->" + category.questions[0].hasOwnProperty('questionsCount'))
console.log(JSON.stringify(category))
console.log(category.questions[0].toJSON().questionsCount)
})
res.json(myUtil.response.paging(data, page, limit))
})
.catch(err => {
console.log("Error get categories: " + err.message)
res.status(500).send({
message: "An error has occured while retrieving data."
})
})
}
I loop through the data.rows to get each category object.
The console.log outputs are:
------ May 31 ----> {"questionsCount":4} -->false
{"id":1,"title":"movies","isActive":true,"createdAt":"2020-05-30T19:26:04.000Z","updatedAt":"2020-05-30T19:26:04.000Z","questions":[{"questionsCount":4}]}
4
https://github.com/sequelize/sequelize/blob/master/docs/manual/core-concepts/model-querying-finders.md
By default, the results of all finder methods are instances of the model class (as opposed to being just plain JavaScript objects). This means that after the database returns the results, Sequelize automatically wraps everything in proper instance objects. In a few cases, when there are too many results, this wrapping can be inefficient. To disable this wrapping and receive a plain response instead, pass { raw: true } as an option to the finder method.
(emphasis by me)
Or directly in the source code, https://github.com/sequelize/sequelize/blob/59b8a7bfa018b94ccfa6e30e1040de91d1e3d3dd/lib/model.js#L2028
#returns {Promise<{count: number, rows: Model[]}>}
So the thing is that you get an array of Model objects which you could navigate with their get() method. It's an unfortunate coincidence that you expected an array, and got an array so you thought it is "that" array. Try the {raw:true} thing, I guess it looks something like this:
db.Category.findAndCountAll({
where: {},
include: [
{
model: db.Question,
as: "questions",
attributes: [[db.Sequelize.fn('COUNT', 'id'), 'questionsCount']]
}
],
offset: limit * page,
limit: limit,
order: [["id", "ASC"]],
raw: true // <--- hopefully it is this simple
}) [...]
toJSON() is nearby too, https://github.com/sequelize/sequelize/blob/59b8a7bfa018b94ccfa6e30e1040de91d1e3d3dd/lib/model.js#L4341
/**
* Convert the instance to a JSON representation.
* Proxies to calling `get` with no keys.
* This means get all values gotten from the DB, and apply all custom getters.
*
* #see
* {#link Model#get}
*
* #returns {object}
*/
toJSON() {
return _.cloneDeep(
this.get({
plain: true
})
);
}
So it worked exactly because it did what you needed, removed the get() stuff and provided an actual JavaScript object matching your structure (POJSO? - sorry, I could not resist). I rarely use it and thus always forget, but the key background "trick" is that a bit contrary to its name, toJSON() is not expected to create the actual JSON string, but to provide a replacement object which still gets stringified by JSON.stringify(). (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON_behavior)
try to do so category.data[0].questions.questionCount
As mentioned by others already, you need category.data[0].questions[0].questionCount.
Let me add to that by showing you why. Look at your object, I annotated it with how each part would be accessed:
category = { // category
"page": 0,
"limit": 10,
"total": 2,
"data": [ // category.data
{ // category.data[0]
"id": 1,
"title": "movies",
"createdAt": "2020-05-30T19:26:04.000Z",
"updatedAt": "2020-05-30T19:26:04.000Z",
"questions": [ // category.data[0].questions
{ // category.data[0].questions[0]
"questionCount": 2 // category.data[0].questions[0].questionCount
}
],
"questionsCount": "newValue here!"
}
]
}
try this
category.data[0].questions[0].questionCount
the reason why you have to use toJSON is because it's sometimes it is used to customise the stringification behavior. like doing some calculation before assinging the value to the object that will be returned , so it is most likley been used here to calculate the "numb of questions and then return an object with the property questionscount and the number calculated
so the object you retreived more or less looks like this
var cathegory = {
data: 'data',
questions:[{
// some calulation here to get the questionsCount
result=4,
toJSON () {
return {"questionsCount":this.result}
}
}
]
};
console.log(cathegory.questions[0].toJSON().questionsCount) //4
console.log(JSON.stringify(cathegory)) // {"data":"data","questions":[{"questionsCount":4}]}
console.log("------ May 31 ----> " + JSON.stringify(cathegory.questions[0]) + " -->" + cathegory.questions[0].hasOwnProperty('questionsCount')) //false
Related
Currently I store some data in FaunaDB every week. This is done using a cronjob. In my code I'm trying to fetch the documents from only the last two weeks. I'd like to use the timestamp to do so.
One of the documents to fetch:
{
"ref": Ref(Collection("weeklyContributors"), "350395411XXXXXXXX"),
"ts": 1670421954340000,
"data": {
...allMyDataFields
}
}
My code
const now = Date.now() * 1000;
const twoWeeksAgo = (Date.now() - 12096e5) * 1000;
console.log(now); //returns 1670493608804000
console.log(twoWeeksAgo); // returns 1669284008804000
// the stored document has a timestamp of 1670421954340000, so this should be in between [now] and [twoWeeksAgo]
await client.query(
q.Paginate(
q.Range(
q.Match(q.Index("get_weekly_list_by_ts")),
twoWeeksAgo,
now
)
)
);
This is a screenshot of the index I created in Fauna
Above code should fetch all documents where the timestamp's between now and twoWeeksAgo but it returns an empty array (so no documents match the query). Above code doesn't generate any errors, it does return a statuscode 200, so syntax should be fine. Why can't I fetch the document I gave in this example?
UPDATE
Found the solution for the index. The index should filter on Values, not Terms. Enter TS and Ref returns the document. BUt now I don't know how to get the corresponding document.
This returns an error
await client.query(
q.Map(
q.Paginate(
q.Range(
q.Match(q.Index("get_weekly_list_by_ts")),
twoWeeksAgo,
now
)
),
q.Lambda((x) => q.Get(x))
)
);
Changed index screenshot here
Congratulations on figuring out most of the answer for yourself!
As you deduced, the terms definition in an index specifies the fields to search for, and the values definition specifies the field values to return for matching entries.
Since you added the document reference to the values definition, all that you need now is to fetch that document. To do that, you need to Map over the results.
The following example uses Shell syntax, and involves sample documents that I created with a createdAt field recording the creation timestamp (since ts is the last-modified timestamp):
> Map(
Paginate(
Range(
Match(Index("get_weekly_list_by_ts")),
TimeSubtract(Now(), 14, "days"),
Now()
)
),
Lambda(
["ts", "ref"],
Get(Var("ref"))
)
)
{
data: [
{
ref: Ref(Collection("weeklyContributors"), "350498857823502848"),
ts: 1670520608640000,
data: { createdAt: Time("2022-12-01T17:30:08.633Z"), name: 'Fourth' }
},
{
ref: Ref(Collection("weeklyContributors"), "350498864657072640"),
ts: 1670520615160000,
data: { createdAt: Time("2022-12-07T17:30:15.152Z"), name: 'Fifth' }
}
]
}
Since your index returns ts and ref, notice that the Lambda function accepts both parameters in an array. The Lambda parameters have to match the number returned by the index. Then the Lambda calls Get to fetch the document.
In case you're wondering, here's the index definition that I used for my example:
> Get(Index("get_weekly_list_by_ts"))
{
ref: Index("get_weekly_list_by_ts"),
ts: 1670520331720000,
active: true,
serialized: true,
name: 'get_weekly_list_by_ts',
source: Collection("weeklyContributors"),
values: [ { field: [ 'data', 'createdAt' ] }, { field: [ 'ref' ] } ],
partitions: 8
}
My index is misnamed: I used the same name from your original query to help you correlate what is being used.
Note: there is no need to mask the document ID in a document that you share. It is only valid for the database containing the document.
I have setup mongo streams for a product collection below but an update in any fields of the specs object returns the whole specs object instead of the updated field. I would expect a change of the display field to only return the display field rather than of the whole specs object
product collection on mongo db
{
"productName": "Apple iPhone 5",
"specs": {
"network": "GSM / CDMA / HSPA / EVDO / LTE",
"display": "IPS LCD",
"memory": "16GB 1GB RAM"
}
}
const productPipeline = [
{
$project: {
'fullDocument.productName': 1,
'updateDescription.updatedFields.specs': 1,
},
},
];
const productChangeStream = this.productModel.watch(productPipeline, {
fullDocument: 'updateLookup',
});
productChangeStream.on('change', async (data) => {
console.log(JSON.stringify(data, null, 2));
});
I have tried to use the productPipeline below but still did not work
const productPipeline=[
{
$project: {
'fullDocument.productName': 1,
'updateDescription.updatedFields.specs.network': 1,
'updateDescription.updatedFields.specs.display': 1,
'updateDescription.updatedFields.specs.memory': 1,
},
},
];
It looks behaviour is due to the difference between an update and a replace operation. MongoDB has two forms of update; a delta-update and a full-document replacement.
Replacements look like this:
db.coll.update({query for matching document}, {full replacement document})
OR
db.collection.replaceOne({query for matching document}, {full replacement document})
Normal (delta) updates look like this:
db.coll.update({query for matching document}, {$set: {individualField: newValue}})
If your application is performing full replacements, then the complete replacement document will be recorded in the oplog. Since change streams simply reports what it sees in the oplog, this will produce a change stream event with {operationType: "replace"} and a complete copy of the new document.
If, however, you do a normal update, then only the fields that were changed will be reported; you will receive a change stream event with {operationType: "update"} and a field called updateDescription which reports the fields that were modified by the operation. Please see this page for details of the update and replace events, and the updateDescription field.
For instance, if I insert the sample document you provided above and then run the following update:
db.testing.updateOne({}, {$set: {"branding.control_panel.companyLogo.displayName": "1200px-Logo_NIKE_updated.svg"}})
... then I receive the following change event (abbreviated for clarity):
{
"_id" : ...,
"operationType" : "update",
"clusterTime" : ...,
"ns" : ...,
"documentKey" : ...,
"updateDescription" : {
"updatedFields" : {
"branding.control_panel.companyLogo.displayName" : "1200px-Logo_NIKE_updated.svg"
},
"removedFields" : [ ]
}
}
Here is the Jira issue
What im trying to do is to get only the source from the elastic search query in order to skip the processing on the javascript, so i could squize some more performance gains.Is it possible to do so?
So here is what i currenly do, i just get the data from the elastic search and iterate every one of those objects in order to construct an array that only contains the what's inside of _source:
const { body } = await this.elsticSearchService.search<any>({
index: this.configService.get('ELASTICSEARCH_INDEX'),
body: {
query: {
match_all: {},
},
size: 10000,
},
});
const hits = body.hits.hits;
return hits.map((item: any) => item._source);
So my question is, is there a way to only get the _source from the elastic search, in order
to skip the processing on the JavaScript?
So the object returned from the elastic search would look like this
[
0:{ "key": "value"}, // key,value from _source object
1:{ "key": "value"}, // key,value from _source object
2:{ "key": "value"}, // key,value from _source object
]
so without all of the other fields like hits, took etc...
It's not possible to change the structure of the response you get from the search call.
However, what you can do is to specify filter_path so you only get the _source content in the response and you wouldn't need to process it since you know you only have _source content
const { body } = await this.elsticSearchService.search<any>({
index: this.configService.get('ELASTICSEARCH_INDEX'),
filter_path: '**._source', <--- add this line
body: {
query: {
match_all: {},
},
size: 10000,
},
});
The Backend has an end-point called api/Options/GetEmailMessageTemplate, it returns objects which has this schema:
{
messageType: string, Enum: [ ConfirmationEmail,
ConfirmationTransaction, RecoveryPassword, SetupAdminAccount, ConfirmationApiKey, ConfirmationDepositTransaction ]
language: string, Enum: [ En, Ru, Zh, Es, Et ]
subject: string,
template: string,
isUsed: boolean
}
e.g. response:
{
"messageType": 1,
"language": "En",
"subject": "string",
"template": "string",
"isUsed": true
}
here is another end-point to edit it api/Options/Options/UpdateEmailMessageTemplate which consumes json with the same schema as above. messageType might be either number of an element in Enum or Enum value (e.g. 'ConfirmationEmail')
On the Frontend to list all the data and be able to change it I came up with this approach:
I made an strictly ordered array:
messageTypes: [
{
name: 'Confirmation Email',
success: false,
},
...
]
Success is required to show if the change of this template was successful
I get messageTypeas a number id from backend, I just used it as index in my array (so, for this to work my array must be ordered in the exactly same way Enum of that field is ordered ), to get the name of that messageType and operate with success field
3.api/Options/Options/UpdateEmailMessageTemplate gets messageType using index of the currently being edited element (indexOf)
While this approach worked as was expected I can't help but think there was a better way to handle this.
I would like to hear if there are better practices to handle that
Based on my understanding, you are wanting a way to work with a friendly list of values as well as their id's. One approach would be to create two separate classes. This would enable you to feed the raw response to a single model and you can add whichever methods are needed to translate id > name or the other way around.
You could probably get a little more fancier and use get/set but I'm still a little foggy on the requirements. But, here's an approach that I would take:
/**
* Create a class that knows how to translate it
* Bonus points: this could be populated via your endpoint
* that returns your enum list so you don't have to keep
* updating it if there's a change on the backend
*/
class MessageType {
constructor(messageType) {
this.id = messageType;
const messageTypes = [
'ConfirmationEmail',
'ConfirmationTransaction',
'RecoveryPassword',
'SetupAdminAccount',
'ConfirmationApiKey',
'ConfirmationDepositTransaction'
];
this.name = messageTypes[this.id];
}
}
/**
* Create a class / model for your response.
* This will enable you to add any methods
* needed for translating things how you need
* them. For example, you might want a method
* to convert your model into what the backend
* expects.
*/
class MessageTemplate {
constructor(response) {
this.messageType = new MessageType(response.messageType);
this.language = response.language;
this.subject = response.subject;
this.template = response.template;
this.isUsed = response.isUsed;
}
getJsonPayloadForBackend() {
const payload = { ...this };
payload.messageType = payload.messageType.id;
return payload;
}
}
// Usage
const template = new MessageTemplate({
"messageType": 2,
"language": "En",
"subject": "string",
"template": "string",
"isUsed": true
});
console.log(template);
console.log('data to send to backend', template.getJsonPayloadForBackend())
I want to add a new object for each nested array. I'm calling this function any time I add a product to my orderintake:
add2order(productID, productName, productRatePlans) {
this.orderIntake.push({ productID, productName, productRatePlans });
let i = 0;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges.forEach(element => {
i++;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].quantity = this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].defaultQuantity;
});
}
this is an example response from the server:
{
"id": "8adc8f996928b9a4016929c59b943a8f",
"sku": "SKU-00006778",
"Partner_Account_ID__c": null,
"productRatePlans": [
{
"id": "8adce4216928c28d016929c59bff3372",
"status": "Active",
"name": "Enterprise",
"description": null,
"effectiveStartDate": "2016-02-26",
"effectiveEndDate": "2029-02-26",
"productRatePlanCharges": [
{
"id": "8adc8f996928b9a4016929c59d183a92",
"name": "USAGE_COUNTER_2",
"type": "Usage",
"model": "Volume",
"uom": "Each",
"pricingSummary": [
"Up to 5000 Each: USD0 flat fee"
],
"pricing": [
{
...
}
],
"defaultQuantity": null,
"applyDiscountTo": null,
"discountLevel": null,
"discountClass": null,
...
"financeInformation": {
..,
}
}
]
}
],
"productFeatures": [
{
...
}
]
}
The data is being retrived this way from an external REST backend so unfortunately I can't initialize the data including the new property...
so in every productRatePlanCharges there should be 1 new object 'quantity'.
How can I add this field to every productRatePlanCharges?
Right now I'm getting: ERROR
TypeError: Cannot read property 'productRatePlanCharges' of undefined
And how can I make sure I'm always adding this to the last orderIntake element? Don't mind productRatePlans there is only 1 in each orderintake...
thanks for your support!
Here you have to create productDetails object with inititalised array like below so that you won't get the error.
add2order(productID, productName, productRatePlans) {
// Create object like below
let productDetails = { productID : productID, productName : productName, productRatePlans : productRatePlans
}
this.orderIntake.push(productDetails);
let i = 0;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges.forEach(element => {
i++;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].quantity = this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].defaultQuantity;
});
}
Hope this will help!
as you used Angular you probably use Typescript too. I recommend that you create a model like your incoming model and there define your quantity: number inside productRatePlanCharges object. then map the incoming data to your own model. therefore you will have a quantity=0 in your model that you can change it later in a loop.
If you want to continue with your own way take a look at this:
Add new attribute (element) to JSON object using JavaScript
there is no problem to add an element to current model almost like you did, and the problem might be somewhere else as your error refers to existence of productRatePlanCharges!
as you used forEach I prefer to use that 'element' and double iterating with i++; is not a good idea to me.
this might be better:
element.quantity = element.defaultQuantity;