Suppose I have a very simple Schema like this:
const PlayerSchema = new Schema({
Region: {type: String},
Tier: {type: String},
Division: {type: String}
});
And further suppose that PlayerSchema lives in a MongoDB collection, where making a GET request to "/players" return a list of all the players in the collection:
router.get("/players", function(req, res){
DbSchemas.PlayerSchema.find({}).then(function(players){
res.send(players);
});
});
This is the response I get from making that GET request to "/players":
[
{
"Region": "North America",
"Tier": "Gold",
"Division": "III"
},
{
"Region": "Asia",
"Tier": "Platnium",
"Division": "IV"
}
]
However, suppose now I want to add some custom data to the PlayerSchema that are not in the database. I want the result returned to be something like this:
[
"code": 200,
"status": "OK",
"data": {
{
"Region": "North America",
"Tier": "Gold",
"Division": "III"
},
{
"Region": "Asia",
"Tier": "Platnium",
"Division": "IV"
}
]
Basically, I still want the MongoDB collection to store only the PlayerSchema (without the code and status), I would like to append the code:200 and status:OK myself if possible.
Here's what I tried. It doesn't work. I start by creating another schema:
const PlayerDataSchema = new Schema({
code: {type: Number},
status: {type: String},
data: {
type: [PlayerSchema]
}
});
Then, I tried to create an instance of PlayerDataSchema using the constructor, and assigning the PlayerSchema object to PlayerDataSchema.
router.get("/players", function(req, res){
DbSchemas.PlayerSchema.find({}).then(function(players){
var PlayerData = new DbSchemas.PlayerDataSchema({ ResponseCode:200 }, { ResponseStatus:"OK" }, {Players: players});
res.send(PlayerData);
});
});
This doesn't work and just gives me a TypeError: DBSchemas.PlayerDataSchema is not a constructor.
How do I add custom data into a schema?
You don't need to modify the schema to add fields to the response.
Your desired output isn't valid json:
[
"code": 200,
"status": "OK",
"data": {
{
"Region": "North America",
"Tier": "Gold",
"Division": "III"
},
{
"Region": "Asia",
"Tier": "Platnium",
"Division": "IV"
}
]
Arrays contain objects, and you're putting key-pairs in. That won't work.
That said, you just need to take the response and manipulate it with javascript to make it whatever you need it to be.
For example, if your retrieved data is this array:
players = [
{
"Region": "North America",
"Tier": "Gold",
"Division": "III"
},
{
"Region": "Asia",
"Tier": "Platnium",
"Division": "IV"
}
]
You can make another object to return like this:
result = {
code: 200,
status: "OK",
data: players
};
So your return value would be:
{
code: 200,
status: "OK",
data: [
{
"Region": "North America",
"Tier": "Gold",
"Division": "III"
},
{
"Region": "Asia",
"Tier": "Platnium",
"Division": "IV"
}
]
};
Which is an object with 3 keys, a number, a string, and an array.
I hope that sets you on the right course.
Related
Developing a node.js api. I have mongo data that shows venues. It contains an address, for example....
"address": {
"street": "123 main st",
"city": "seomwhere",
"state": "FL",
"zip": "33222"
},
:
When I POST to the update endpoint and send the body like this it does update the address, but it REMOVES all the other fields that were there....
"address": {
"zip": "33222"
}
So, in the db, street, city, state are missing. Here's a piece of my code...
venue = await Venue.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});
Any ideas why?
Because address contains only single key in the body ("address": { "zip": "33222" }) it replaces the other values inside the address object with the blank ones in the database.
So to overcome the issue you have to use . notation with the address object. Like
venue = await Venue.findByIdAndUpdate(
req.params.id,
{ $set: { "address.zip": "33222" },
{ new: true, runValidators: true }
)
But as you cannot identify which keys inside the address object will be updated and also for the root level
const object = req.body
for (var key in req.body.address) {
object[`address.${key}`] = object.address[key]
}
delete object.address
venue = await Venue.findByIdAndUpdate(
req.params.id,
{ $set: object },
{ new: true, runValidators: true }
)
You are overwriting existing address fields when not specified in the request body.
You need to only update the fields in the request body:
const fields = {};
Object.keys(req.body.address).forEach(key => {
fields[`address.${key}`] = req.body.address[key];
});
const venue = await Venue.findByIdAndUpdate(req.params.id, fields, {
new: true,
runValidators: true
});
Let's say we have this venue document:
{
"_id": "5e047d4e10be4f0da4c61703",
"name": "Venue 1",
"address": {
"street": "123 main st",
"city": "seomwhere",
"state": "FL",
"zip": "33222"
},
"__v": 0
}
When you use a requset body like this:
{
"address": {
"zip": "33333"
}
}
The response will be:
{
"address": {
"street": "123 main st",
"city": "seomwhere",
"state": "FL",
"zip": "33333"
},
"_id": "5e047d4e10be4f0da4c61703",
"name": "Venue 1",
"__v": 0
}
And when we want to update like this:
{
"address": {
"state": "NY",
"zip": "44444"
}
}
The response will be:
{
"address": {
"street": "123 main st",
"city": "seomwhere",
"state": "NY",
"zip": "44444"
},
"_id": "5e047d4e10be4f0da4c61703",
"name": "Venue 1",
"__v": 0
}
I have schema like :
const UserSchema = new mongoose.Schema({
skills: [{
_id: false,
name: String,
level: Number,
exp: Number
}]
});
As example document is like :
{
"_id": {
"$oid": "5b0941419703f80a121c44df"
},
"skills": [
{
"name": "woodcutting",
"exp": 1110,
"level": 1
},
{
"name": "smithing",
"level": 0,
"exp": 0
},
{
"name": "reading",
"level": 0,
"exp": 0
},
{
"name": "writing",
"level": 0,
"exp": 0
}
]
}
So I just need to update exp and level of woodcutting and smithing in one query, is it possible, if possible, how?
Or will I need to change all array and set(replace) with it value of skills?
You can use arrayFilters from mongodb 3.6
db.collection.update(
{ },
{ "$set": {
"skills.$[skill1].exp": "your_value",
"skills.$[skill2].level": "your_value",
"skills.$[skill2].exp": "your_value",
"skills.$[skill1].level": "your_value"
}},
{ "arrayFilters": [{ "skill1.name": "woodcutting", "skill2.name": "smithing" }] }
)
I am using the Hapi.js framework along with Joi for data validation. I am trying to validate a JSON file using Joi. I have defined a schema and wanted to check whether the JSON file had all the fields from my schema.
Some of the string fields can be empty. In my schema file when I defined min as 0, it is saying name is a required field.
I am using the schema below:
module.exports = {
"name": { "type": "string", "min": 0, "max": 30},
"age": { "type": "number", "min": 1, "max": 36},
"dob": { "type": "string", "min": 0, "max": 100 }
}
How can I modify this schema to handle empty strings?
If you want to allow empty strings, you need to explicitly allow them with joi.string().allow('').
var joi = require('joi');
var schema = joi.object().keys({
name: joi.string().min(0).allow('').allow(null),
age: joi.number().min(1).max(36),
dob: joi.string().min(0).max(100)
});
var obj = {
name: '',
age: '18',
dob: '11/11/2998'
};
var result = joi.validate(obj, schema);
console.log(JSON.stringify(result, null, 2));
The output of the above schema after using joi.describe is:
{
"type": "object",
"children": {
"name": {
"type": "string",
"valids": [
"",
null
],
"rules": [
{
"name": "min",
"arg": 0
}
]
},
"age": {
"type": "number",
"invalids": [
null,
null
],
"rules": [
{
"name": "min",
"arg": 1
},
{
"name": "max",
"arg": 36
}
]
},
"dob": {
"type": "string",
"invalids": [
""
],
"rules": [
{
"name": "min",
"arg": 0
},
{
"name": "max",
"arg": 100
}
]
}
}
}
This is my schema:
var RegionSchema = new Schema({
"metadata": {
"type": String,
"name": String,
"children": [{
"name": String,
"type": String
}],
"parent": Schema.ObjectId
},
"data": [DataContainer]
});
This is a unit test I am writing, in which I store an instance of this object with some null values:
describe('Region with no data', function() {
it('Should save without error', function(done) {
var emptyRegion = new Region({
"metadata": {
"type": "City",
"name": "San Diego",
"children": [],
"parent": null
},
"data": []
});
emptyRegion.save(function(err, saved) {
console.log(saved)
if (err) throw err;
if (saved.metadata.name === "San Diego")
done();
})
});
});
However, when I try to print out what is saved, I get this:
{ __v: 0, _id: 551cd261cc55c5ff48c8150b, data: [] }
Why is my metadata object not saving? Even before the save call, emptyRegion looks just like that. Am I not defining my metadata correctly?
The annoying culprit is the type field within the metadata subdocument. Mongoose interprets that as meaning that metadata is of type String and has a bunch of irrelevant properties. Change your schema definition to the following and it should work:
var RegionSchema = new Schema({
"metadata": {
"type": {"type": String},
"name": String,
"children": [{
"name": String,
"type": {"type": String}
}],
"parent": Schema.ObjectId
},
"data": [DataContainer]
});
Alternatively, use a different name for your type fields.
What I am trying to do is the following.
I have objects that could live in multiple documents, and although I am not sure if this is best practice, I am using their ObjectId as pointers.
Document A
{
"_id": {
"$oid": "51a02dade4b02780aeee5ab7"
},
"title": "My Document A",
"artifacts": [
"51a81d8ee4b084336fea2d33",
"asdfsdfe4b084336fea2d33"
]
}
Document B
{
"_id": {
"$oid": "51a02dade4b02780aeee5ab7"
},
"title": "My Document A",
"artifacts": [
"51a81d8ee4b084336fea2d33",
"123454b084336fea2d33"
]
}
The individual artifact looks something like. If this artifact changes then there is no need to update the documents that it lives in:
{
"_id": {
"$oid": "51a81d8ee4b084336fea2d33"
},
"title": "Artifact A",
"media": {
"player": "video",
"source": "http://www.youtube.com/watch?v=12334456"
}
}
What I'd like to do, is get an expanded list of all the artifacts shown in either doc A or doc B in an array something like:
[{
"_id": {
"$oid": "51a81d8ee4b084336fea2d33"
},
"title": "Artifact A",
"media": {
"player": "video",
"source": "http://www.youtube.com/watch?v=12334456"
}
},
{
"_id": {
"$oid": "123455e4b084336fea2d33"
},
"title": "Artifact B",
"media": {
"player": "video",
"source": "http://www.youtube.com/watch?v=12334456"
}
}]
The way I have done it in the past is by POSTing from client a list of Ids and then using
{_id: { $in: userQuestArray} }
but if i am constantly posting back to node from the client it seems a bit inefficient. Especially if the array I am posting is hundreds of items long.
Mongoose provide you a feature population which exactly does the same thing you are looking for http://mongoosejs.com/docs/populate.html. In your case what you need to do is
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var tempSchema = Schema({
title : String,
artifacts : [{ type: Schema.Types.ObjectId, ref: 'Artifacts' }]
});
var artifactsSchema = Schema({
title : String,
media : { player: String, source: String, }
});
var tempModel = mongoose.model('Temp', tempSchema);
var artifactsModel = mongoose.model('Artifacts', artifactsSchema);
Now whenever artifacts documents gets created you need to push that in respective temp dcoument artifacts array as shown in mongoose documentation. You can retrive it like that
tempModel
.find(query)
.populate('artifacts')
.exec(function (err, results) {
if (err)
console.log(err);
console.log(results);
})
//results contains data in form shown below
[{
"_id": {
"$oid": "51a02dade4b02780aeee5ab7"
},
"title": "My Document A",
"artifacts": [{
"_id": {
"$oid": "51a81d8ee4b084336fea2d33"
},
"title": "Artifact A",
"media": {
"player": "video",
"source": "http://www.youtube.com/watch?v=12334456"
}
},
{
"_id": {
"$oid": "123455e4b084336fea2d33"
},
"title": "Artifact B",
"media": {
"player": "video",
"source": "http://www.youtube.com/watch?v=12334456"
}
}]
}]