How do you validate an array in Joi with only one item? - joi

I would like to accept an array of strings as an argument with Joi but when I pass only one item I receive the error value must be an array. If I try to pass a second value that's empty to force array syntax conversion in Express I receive must be one of... error after defining the strict set of possible values as empty is not strictly allowed.
Request schema:
const requestSchema = Joi.object().keys({
query: Joi.object().keys({
endTime: Joi.string().required(),
fields: Joi.array().items(
Joi.string().valid(
"value1",
"value2",
"value3"
)
),
startTime: Joi.string().required()
})
});

The key here is the single() function. Implementing this will allow you to pass a single value for an array and Joi will wrap the single value in an array so that it validates correctly:
const requestSchema = Joi.object().keys({
query: Joi.object().keys({
endTime: Joi.string().required(),
fields: Joi.array().items(
Joi.string().valid(
"value1",
"value2",
"value3"
)
).single(),
startTime: Joi.string().required()
})
});
This allows you to send fields=value1 which gets converted to: fields: ["value1"].

Related

Do not allow unknown keys in nested object or array of objects using JOI?

If an unknown field comes in the parent/root object then it throws the expected error. How to throw an error if there are any unknown fields in nested objects?
As per the Joi object.unknown doc, it says does not apply to children so what should I use to get my expected results?
const obj = {
data: {
center: "NY"
},
city: "tx",
state: "us"
};
const schema = Joi.object({
data: Joi.object({
name: Joi.string().required()
}).required(),
city: Joi.string.required(),
state: Joi.string.required(),
});
schema.validate(obj);
// outputs
name is required field
// expected output
name is required field
center is not allowed

omit empty strings fields mongoose

I have the following schema:
const mySchema = new mongoose.Schema({
x: String,
y: String
})
when a user from the front-end requests in his body:
req.body = {
'x' : '',
'y': ''
}
this results in creating a field in MongoDB, but with an empty string.
I need a way to prevent this behavior by setting the empty strings to be undefined somehow.
Is there such an option in Mongoose? or do I have to predict my own middlewares for that?
You could use the set method for Mongoose Schemas:
const mySchema = new mongoose.Schema(
{
myAttribute: {
type: String,
set: (attribute: string) => attribute === '' ? undefined : attribute,
},
},
{ strict: 'throw' },
);
This will unset the field if the string equals ''.
Use this to trim the strings:
set: (a: string) => a?.trim() === '' ? undefined : a
You don't need mongoose, or a middleware to handle this. You can just write a quick few lines to check for empty values and exclude them from the MongoDB write operation.
Ex:
const newEntry = Object.entries(req.body).reduce((obj, [key, value]) => {
if (value) obj[key] = value
return obj
}, {})
In this example, I convert the req.body into an array using Object.entries and iterate over it with the Array.reduce method, wherein I add key:value pairs to a new object if there is a value to add. Since an empty string value is falsey I can do a simple if check on the value. I then assign the return of the reduce method to the variable newEntry. Then I would then take the new entry and create the MongoDB document with it.
This could be extracted into a helper method and reused in any of your routes that need to check remove empty values from an object.
Docs on Array.reduce
Docs on Object.entries

MongoDB field only accepts 3 special values

slider_value: {
type: Number,
required: false,
},
This is the Mongoose schema for one of the fields in my MongoDB model.
It may only accept the integer values of 1, 4, and 10.
How can this validator be specified in the schema?
If you only need to store either one of these three values, storing them as a string, and validating using the enum key would be reasonable. For example that could look like this:
{
slider_value: {
type: String,
enum: ["1", "4", "10"],
},
}
Alternatively, if it is a requirement to store them in form of an int, you could use a custom validator to check a value before it's saved. That would look like this:
{
slider_value: {
type: Number,
validate: {
validator: value => value === 1 || value === 4 || value === 10,
message: props => `${props.value} is invalid for slider_value`,
},
},
}
For more details on custom validators and validation in mongoose in generell, here are the mongoose validation docs.

How to validate array which is dynamic in nature Like sometime string and some time objects

I am Using #hapi/joi.
I have an array which store value dynamically, it store string, empty string (an empty array) or an Objects ({"userid": "111jh2jh322j3h2j3h", "msg": 1}).
So it will be like this:
type1-> member: []
type2-> member: ["firstString", "secondString"]
type3-> member: [{"userid": "111jh2jh322j3h2j3h", "msg": 1}, {"userid": "7875jh2jh3545hj3hth", "msg": 0}]
I am confused that how to do validation on #Hapi/joi.
Currently My implementation is:
member: Joi.array().items(Joi.string().allow('')),
I know that If we have an object which is stored under an array then I will do the validation is like:
member: Joi.array().items(Joi.object({
userid: Joi.string(),
msg: Joi.number(),
})),
Any help is really appreciated for that. Thanks in advance.
It will be like this:
member: Joi.array().items(Joi.string().allow(''), Joi.object({
userid: Joi.string(),
msg: Joi.number(),
})),

Nested maps in mongoose schema

I'm currently creating a fitting mongoose schema for our new JSON format.
It is not very complex but I ran into the issue that certain values are not saved as array but rather as "normalized array" like this:
answers: [{value: 5, string: "abc"}, {value: 4, string: "def"}]
will be:
answers: {
1: {id: 1, value: 5, string: "abc"},
2: {id: 2, value: 4, string: "def"}
}
The Objects themselves can have nested "normalized arrays" as well.
For now I tried using mongoose type "Map" in the top-level-Schema like this:
answers: {
type: Map,
of: answer
}
Where "answer" is a separate mongoose.Schema itself.
But all I get is:
TypeError: Undefined type `Map` at `answers`
Did you try nesting Schemas? You can only nest using refs or arrays.
Why can't I just nest maps as expected? Is it even possible to project this "normalize array" structure in a mongoose schema, and if so, how?
Thank for reading!
I was in the same situation and it seems to work for me:
const ChildSchema = new Schema({
firstName: String,
});
const ParentSchema = new Schema({
name: String,
mapProperty: { type: Map, of: ChildSchema },
});
I do also get the validation triggered from mongoose on the ChildSchema so it seems to accept it just fine.
Hope it helps.