According to Joi documentation, you can use Joi.object()
like so:
const object = Joi.object({
a: Joi.number().min(1).max(10).integer(),
b: Joi.any()
});
But you can also write an equivalent code using Joi.object().keys()
like so:
const object = Joi.object().keys({
a: Joi.number().min(1).max(10).integer(),
b: Joi.any()
});
What's the difference between the two?
Hapi Joi is an object schema description language and validator for JavaScript objects. With Hapi Joi, we create blueprints or schemas for JavaScript objects (an object that stores information) to ensure validation of key information.
If you're writing your schema once then you do not need to use .keys()
. As their docs say it is "useful" to use .keys()
when added more lines (keys) to your object.
Joi.object().keys([schema]) notation
This is basically the same as
Joi.object([schema])
, but usingJoi.object().keys([schema])
is more useful when you want to add more keys (e.g. callkeys()
multiple times). If you are only adding one set of keys, you can skip thekeys()
method and just useobject()
directly.Some people like to use
keys()
to make the code more explicit (this is style only).
Taken from: https://github.com/hapijs/joi/blob/v8.0.3/API.md#joiobjectkeysschema-notation
I also found this:
There are many ways to use joi. The hapi docs can't show everything. Calling keys() is only recommended when adding keys to an object as it creates another schema
Taken from: https://github.com/hapijs/hapi/issues/2222
Like the documentation states:
object.keys([schema])
Sets OR extends the allowed object keys where:
- schema - optional object where each key is assigned a joi type object. If schema is {} no keys allowed. If schema is null or undefined, any key allowed. If schema is an object with keys, the keys are added to any previously defined keys (but narrows the selection if all keys previously allowed).
Thus by calling Joi.object()
you first create a schema that allows any keys and then by calling .keys([schema])
you extend that schema (basically the same as defining a new schema with Joi.object([schema])
)
So these two are equivalent:
const a = Joi.object({ firstName: Joi.string() });
const b = Joi.object().keys({ firstName: Joi.string() });
You can also extend both schemas created above:
const aExtended = a.keys({ lastName: Joi.string() })
const bExtended = b.keys({ lastName: Joi.string() })
Like stated in the previous answers, sometimes also top level schemas are created using .keys()
for code consistency reasons, but in the end I think it's a matter of personal preference.
The @hapi/joi documentation is not very clear on this (at v17.1.0). The resulting schemas have the same value, and they validate the same. Looking at the source, Object type is a Keys type with only a change that object need not copy keys from the Any type it is defined from.
Welcome to Node.js v12.16.1.
Type ".help" for more information.
> const Joi = require('@hapi/joi')
undefined
> const util = require('util')
undefined
> const object1 = Joi.object({
... a: Joi.number().min(1).max(10).integer(),
... b: Joi.any()
... });
undefined
> const object2 = Joi.object().keys({
... a: Joi.number().min(1).max(10).integer(),
... b: Joi.any()
... });
undefined
> util.format(object1) == util.format(object2)
true
> object1.validate({a: 1, b: 1})
{ value: { a: 1, b: 1 } }
> object2.validate({a: 1, b: 1})
{ value: { a: 1, b: 1 } }
> object1.validate({a: 0})
{
value: { a: 0 },
error: [Error [ValidationError]: "a" must be larger than or equal to 1] {
_original: { a: 0 },
details: [ [Object] ]
}
}
> object2.validate({a: 0})
{
value: { a: 0 },
error: [Error [ValidationError]: "a" must be larger than or equal to 1] {
_original: { a: 0 },
details: [ [Object] ]
}
}
> object1.validate({a: 1, b: 1, c:1})
{
value: { a: 1, b: 1, c: 1 },
error: [Error [ValidationError]: "c" is not allowed] {
_original: { a: 1, b: 1, c: 1 },
details: [ [Object] ]
}
}
> object2.validate({a: 1, b: 1, c:1})
{
value: { a: 1, b: 1, c: 1 },
error: [Error [ValidationError]: "c" is not allowed] {
_original: { a: 1, b: 1, c: 1 },
details: [ [Object] ]
}
}
> object1.validate({a: 1})
{ value: { a: 1 } }
> object2.validate({a: 1})
{ value: { a: 1 } }
> object1.validate({b: 1})
{ value: { b: 1 } }
> object2.validate({b: 1})
{ value: { b: 1 } }
> object1.validate({})
{ value: {} }
> object2.validate({})
{ value: {} }
Difference between the .append(schema)
and .keys(schema)
is also unclear in documentation. The .append(schema)
does not create a new copy if schema is empty, but otherwise it just returns the value from .keys(schema)
. I found no example where this would make a difference.
> util.format(Joi.object({}).keys({a:1})) == util.format(Joi.object({}).append({a:1}))
true
> util.format(Joi.object({}).unknown().keys({a:1})) == util.format(Joi.object({}).unknown().append({a:1}))
true
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With