I need to extract the text value of productid from order but unfortunately i haven't been able to traverse the JSON. Any ideas about how to traverse the nodes in Node JS the simplest way possible ?
{
"order": {
"PRD_SHIRT_048": {
"price": "40.99",
"productId": "PRD_SHIRT_048",
"quantity": "1"
},
"PRD_TOP_047": {
"price": "40.99",
"productId": "PRD_TOP_047",
"quantity": "1"
}
}
}
First of all, you need to decide where you want to extract the data from.
If it is from a file you need to import the file with for example the npm package 'fs'.
Example code:
const fs = require("fs");
const content = fs.readFileSync("content.json");
console.log("Output: \n" + content);
You can use axios to get the json data from a specific url. For example:
axios.get('yoururl')
.then((response) => {
// handle success
console.log(response);
})
.catch((error) => {
// handle error
console.log(error);
});
Then you have extracted the data successfully.
After that you can parse the json content with JSON.parse and that will return an object with all its content.
For example for your code:
const json = `{"order": {
"PRD_SHIRT_048": {
"price": "40.99",
"productId": "PRD_SHIRT_048",
"quantity": "1"
},
"PRD_TOP_047": {
"price": "40.99",
"productId": "PRD_TOP_047",
"quantity": "1"
}
}}`;
const obj = JSON.parse(json);
console.log(obj.order.PRD_SHIRT_048.productId);
If you want, you can iterate over the objects from the order object and get the product id from that.
If I understand your question correctly, you are looking to extract the productIds?
Here is a solution using vanilla javascript
const data = { order: { PRD_SHIRT_048: { price: '40.99', productId: 'PRD_SHIRT_048', quantity: '1' }, PRD_TOP_047: { price: '40.99', productId: 'PRD_TOP_047', quantity: '1' } } };
console.log(Object.keys(data.order));
// => [ 'PRD_SHIRT_048', 'PRD_TOP_047' ]
console.log(Object.values(data.order).map(({ productId }) => productId));
// => [ 'PRD_SHIRT_048', 'PRD_TOP_047' ]
.as-console-wrapper {max-height: 100% !important; top: 0}
Or if you need a more flexible solution (i.e. multiple different paths for the productId, nested productIds etc), you could consider using a library
// const objectScan = require('object-scan');
const data = { order: { PRD_SHIRT_048: { price: '40.99', productId: 'PRD_SHIRT_048', quantity: '1' }, PRD_TOP_047: { price: '40.99', productId: 'PRD_TOP_047', quantity: '1' } } };
console.log(objectScan(['order.*.productId'], { rtn: 'value' })(data));
// => [ 'PRD_TOP_047', 'PRD_SHIRT_048' ]
console.log(objectScan(['order.*'], { rtn: 'property' })(data));
// => [ 'PRD_TOP_047', 'PRD_SHIRT_048' ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
const json = `{"order": {
"PRD_SHIRT_048": [
"price",
"productId",
"quantity"
],
"PRD_TOP_047": [
"price",
"productId",
"quantity"
]
}}`;
const obj = JSON.parse(json);
console.log(obj.order.PRD_SHIRT_048.productId);
Related
Below is my code to display review array data which is part of the restaurant collection object:
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne(
{ reviews: { $elemMatch: { _id : reviewId } } },
{"projection" : { "reviews.$": true }}
)
return r
}
My object looks like:
{
_id: '6176e58679a981181d94dfaf',
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: []
}
My output looks like:
{
"_id": "6174cfb953edbe9dc5054f99", // restaurant Id
"reviews": [
{
"_id": "6176df77d4639898b0c155f0", // review Id
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
]
}
What I want as output:
{
"_id": "6176df77d4639898b0c155f0",
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
How can I get the output as only the review without getting the restaurant ID or the whole object?
So the query operators, find and findOne do not allow "advanced" restructure of data.
So you have 2 alternatives:
The more common approach will be to do this in code, usually people either use some thing mongoose post trigger or have some kind of "shared" function that handles all of these transformations, this is how you avoid code duplication.
Use the aggregation framework, like so:
const r = await restaurantsCollection.aggregate([
{
$match: { reviews: { $elemMatch: { _id : reviewId } } },
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: "$reviews",
as: "review",
cond: {$eq: ["$$review._id", reviewId]}
}
},
0
]
}
}
}
])
return r[0]
I have the following Promise and Promise.all which works and it returns a json object. However, I want to add a key for each return object.
as of now, it returns something
[value: {school object}, value:{students object}, value:{classroom object}]
desired output:
["schools": {school object }, {students object} , {classroom object} ]
Current Implementation:
new Promise((resolve, reject) => {
const school = getschool (webHost, dataSource, req);
const classRooms = getClassRooms(webHost, dataSource, req);
const students = getstudents (webHost,dataSource, req);
Promise.all([school ,classRooms,students ]).then((res) => {
resolve(res);
})
.catch((error) => {
logger.error(`${error}`);
reject(error);
});
});
classroom
{
"metadata": "metadata",
"value": [
{
"class_id": "171717",
"teacher_name": "Science"
}
]
}
School object
{
"metadata": "metadata",
"value": [
{
"id": "2345354",
"schoolName": "Memorial High School"
}
]
}
Student json
{
"metadata": "metadata",
"value": [
{
"id": "1234",
"studentName": "Beck"
},
{
"id": "5678",
"studentName": "Jeck"
}
]
}
Desired Output:
[
{
"class_id":"171717",
"teacher_name":"Science",
"id":"2345354",
"schoolName":"Memorial High School",
"Students":[
{
"id":"1234",
"studentName":"Beck"
},
{
"id":"5678",
"studentName":"Jeck"
}
]
}
]
It seems like you want to merge the objects to make one unified object of custom type, here is what you want to do:
res => resolve(
{
...res[1].value[0],
...res[0].value[0],
Students: res[2].value
}
)
The ... is called spread syntax. It "explodes" the objects and arrays. What we want here is get the internals of classRooms.value[0] merge them with school.value[0] object's internals, and then, add another attribute at the same level with key as Students which is a not-exploded array specified by student.value.
Here I have created a small TS Playground Example for you to play with the syntax and modify the output the way you may seem fit.
If you run it, it prints the desired output:
{
"class_id": "171717",
"teacher_name": "Science",
"id": "2345354",
"schoolName": "Memorial High School",
"Students": [
{
"id": "1234",
"studentName": "Beck"
},
{
"id": "5678",
"studentName": "Jeck"
}
]
}
-- ORIGINAL ANSWER --
Promise.all returns a promise of resolved objects in an array. So, the .then takes the parameter that's an array of resolved objects in the same order. That means your res parameter is an array of school object, students object, and classroom object in that order. you can do the following to "zip" them up.
new Promise((resolve, reject) => {
const school = getschool (webHost, dataSource, req);
const classRooms = getClassRooms(webHost, dataSource, req);
const students = getstudents (webHost,dataSource, req);
Promise.all([school ,classRooms,students ]).then((res) => {
resolve({"schools": res[0], "classRooms" : res[1], "students": res[2]});
})
.catch((error) => {
logger.error(`${error}`);
reject(error);
});
});
or even better,
.then(([schools, classRooms, students]) => {
resolve({schools, classRooms, students});
})
I am creating a schema for an Order model that will track the items ordered along with the quantity purchased. I want to keep the itemId references and the quantity tied together as an array in one parameter.
I have created an Array that includes a reference to the ObjectId plus an additional Number type. I am currently unable to populate the product information using a .populate() query.
Order Schema
const mongoose = require("mongoose");
const { Schema } = mongoose;
const orderSchema = new Schema({
orderNumber: String,
_itemsOrdered: [
{
itemId: {
type: mongoose.Schema.Types.ObjectId,
ref: "menuItems"
},
quantity: Number
}
]
});
mongoose.model("orders", orderSchema);
MenuItem Schema
const mongoose = require("mongoose");
const { Schema } = mongoose;
const MenuItemSchema = new Schema({
imageURL: String,
name_en: String,
name_es: String,
type_en: String,
type_es: String,
description_en: String,
description_es: String,
dietaryCallouts: [String],
price: Number
});
mongoose.model("menuItems", MenuItemSchema);
module.export = MenuItemSchema;
I am able to save the record but cannot populate the MenuItem information with the following query:
Order Controller
async show(req, res, next) {
try {
const orderId = req.params.id;
let order = await Order.findById({ _id: orderId }).populate(
"_itemsOrdered.itemId"
);
res.send(order);
} catch (err) {
res.status(402).send(err);
}
}
Here it the order object that is being saved to the DB.
Order Object
{
"_id": "5dc93b9c0085b8045e0c8aa3",
"orderNumber": "Order 3",
"_itemsOrdered": [
{
"_id": "5dc93b9c0085b8045e0c8aa5",
"itemId": "5dc7f814a2679b47319a79a4",
"quantity": 1
},
{
"_id": "5dc93b9c0085b8045e0c8aa4",
"itemId": "5dc7e5c7de590744c46f93da",
"quantity": 2
}
],
"__v": 0
}
Your order schema must be like this:
const orderSchema = new Schema({
orderNumber: String,
_itemsOrdered: [
{
itemId: { type: mongoose.Schema.Types.ObjectId, ref: "menuItems" },
quantity: Number
}
]
});
And you can use the following route to create an order document.
router.post("/order", async (req, res, next) => {
try {
const { orderNumber, _itemsOrdered } = req.body;
let order = new Order({ orderNumber, _itemsOrdered });
order = await order.save();
res.status(201).send(order);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
});
Sample body: (you need to change ids according to yours)
{
"orderNumber": "Order 1",
"_itemsOrdered": [
{"itemId": "5dc90346222b892434e4675a", "quantity" : 1 },
{"itemId": "5dc90359222b892434e4675b", "quantity" : 2 }
]
}
To get the order and its items you can use populate like this:
router.get("/orders/:id", async (req, res) => {
try {
const orderAndItems = await Order.findById(req.params.id).populate(
"_itemsOrdered.itemId"
);
res.send(orderAndItems);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
});
This will give you a result like this:
{
"_id": "5dc904db8407a217b4dfe6f4",
"orderNumber": "Order 1",
"_itemsOrdered": [
{
"_id": "5dc904db8407a217b4dfe6f6",
"itemId": {
"_id": "5dc90346222b892434e4675a",
"name_en": "item 1",
"price": 1,
"__v": 0
},
"quantity": 1
},
{
"_id": "5dc904db8407a217b4dfe6f5",
"itemId": {
"_id": "5dc90359222b892434e4675b",
"name_en": "item 2",
"price": 2,
"__v": 0
},
"quantity": 2
}
],
"__v": 0
}
I am trying to query an embedded subdocument and then only return an array in that subdocument via projection. After a query you can select fields that you want returned via projection. I want to use the native functionality because it is possible and the most clean way. The problem is it returns arrays in two documents.
I tried different query and projection options, but no result.
User model
// Define station schema
const stationSchema = new mongoose.Schema({
mac: String,
stationName: String,
syncReadings: Boolean,
temperature: Array,
humidity: Array,
measures: [{
date: Date,
temperature: Number,
humidity: Number
}],
lastUpdated: Date
});
// Define user schema
var userSchema = mongoose.Schema({
apiKey: String,
stations : [stationSchema]
}, {
usePushEach: true
}
);
api call
app.get('/api/stations/:stationName/measures',function(req, res, next) {
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations.$.measures': 1,
}
User.findOne(query, options)
.exec()
.then(stations => {
res.status(200).send(stations)
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
});
Expected result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
}
]
}
Actual result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
},
******************************************************
// this whole object should not be returned
{
"stationName": "office",
"measures": []
}
******************************************************
]
}
edit
The answer below with aggregation works, but I still find it odd that I would need so much code. If after my normal query I get the same result with ".stations[0].measures", instead of the whole aggregation pipeline:
.then(stations => {
res.status(200).send(stations.stations[0].measures)
})
The way I read the code, the above does exactly the same as:
const options = {'stations.$.measures': 1}
Where the dollar sign puts in the index 0 as that was the index of the station that matches the query part: stationName: "livingroom"
Can someone explain?
This is not described in terms of mongoose but this will find a particular station name in an array of stations in 1 or more docs and return only the measures array:
db.foo.aggregate([
// First, find the docs we are looking for:
{$match: {"stations.stationName": "livingroom"}}
// Got the doc; now need to fish out ONLY the desired station. The filter will
// will return an array so use arrayElemAt 0 to extract the object at offset 0.
// Call this intermediate qqq:
,{$project: { qqq:
{$arrayElemAt: [
{ $filter: {
input: "$stations",
as: "z",
cond: { $eq: [ "$$z.stationName", "livingroom" ] }
}}, 0]
}
}}
// Lastly, just project measures and not _id from this object:
,{$project: { _id:0, measures: "$qqq.measures" }}
]);
$elemMatch operator limits the contents of an array field from the query results to contain only the first element matching the $elemMatch condition.
Try $elemMatch in Select Query as below :
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations' : {$elemMatch: { 'stationName' : req.params.stationName }}
}
How to update the multiple documents in MongoDB and set the value of the element in an increasing order?
I have got the document as follows
{
"_id" : ObjectId("5b162a31dfaf342dc44c920d")
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920f")
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920c")
}
How can I update the whole documents with a single query so that I can have a new element called "order" in every single field in an increasing order as below
{
"_id" : ObjectId("5b162a31dfaf342dc44c920d"),
"order": 1
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920f"),
"order": 2
}
{
"_id" : ObjectId("5b162a31dfaf342dc44c920c"),
"order": 3
}
Currently I am using the following way to solve the problem
for(let i = 0; i <= req.body.id.length;i++) {
const queryOpts = {
_id: ObjectId(req.body.id[i])
};
const updateOpts = {
$set: {
'order': i + 1
}
};
const dataRes = await req.db.collection('GalleryImage').updateOne(queryOpts, updateOpts);
if(i === req.body.id.length-1) {
return commonHelper.sendResponseMessage(res, dataRes, {
_id: req.body.id
}, moduleConfig.message.updateGalleryOrder);
}
If there any better way than this so that it would not be the expensive operation if there are large number of documents ?
Use bulkWrite() with Array.map() to construct the statement:
try {
let response = await req.db.collection('GalleryImage').bulkWrite(
req.body.id.map((_id,order) =>
({ updateOne: {
filter: { _id: ObjectId(_id) },
update: {
$set: { order: order+1 }
}
}})
)
);
} catch(e) {
// deal with any errors
}
Array.map() has the "index" of the array element being processed within it's second function argument. So simply use that to get the order and set that on all statements.
Rather than writing/responding with the database n times, this only needs happen "once".
There is no other way to get a "sequence" other than introducing it yourself, but at least we can do it with "one" write this way instead of several. Note also to "trap your possible errors" when using async/await syntax.
Example listing
const { MongoClient, ObjectID: ObjectId } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const data = [
"5b162a31dfaf342dc44c920d",
"5b162a31dfaf342dc44c920f",
"5b162a31dfaf342dc44c920c"
];
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const client = await MongoClient.connect(uri);
let db = client.db('test');
// Set up
await db.collection('gallery').removeMany({});
await db.collection('gallery').insertMany(
data.map(_id => ({ _id: ObjectId(_id) }))
);
// Update with indexes
let response = await db.collection('gallery').bulkWrite(
data.map((_id,idx) =>
({
updateOne: {
filter: { _id: ObjectId(_id) },
update: { $set: { order: idx+1 } }
}
})
)
);
log({ response });
let items = await db.collection('gallery').find().toArray();
log({ items });
client.close();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
And the output
{
"response": {
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 3,
"nModified": 3,
"nRemoved": 0,
"upserted": [],
"lastOp": {
"ts": "6563535160225038345",
"t": 18
}
}
}
{
"items": [
{
"_id": "5b162a31dfaf342dc44c920d",
"order": 1
},
{
"_id": "5b162a31dfaf342dc44c920f",
"order": 2
},
{
"_id": "5b162a31dfaf342dc44c920c",
"order": 3
}
]
}
Clearly shows nMatched: 3 and nModified: 3 just as is expected.