Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between Joi.object() and Joi.object().keys()?

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?

like image 512
Berry Avatar asked Nov 12 '19 03:11

Berry


People also ask

What is Joi Object ()?

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.


3 Answers

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 using Joi.object().keys([schema]) is more useful when you want to add more keys (e.g. call keys() multiple times). If you are only adding one set of keys, you can skip the keys() method and just use object() 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 image 198
Luke Brown Avatar answered Oct 07 '22 05:10

Luke Brown


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() })

Which one to use then?

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.

like image 5
Tarmo Terimaa Avatar answered Oct 07 '22 03:10

Tarmo Terimaa


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
like image 3
Marko Kohtala Avatar answered Oct 07 '22 03:10

Marko Kohtala