Joi: Question: how to extend joi to add some key by rule?

Created on 26 May 2017  路  12Comments  路  Source: sideway/joi

Context

  • node version: 7.10.0
  • joi version: 10.3.1
  • environment (node, browser): node
  • used with (hapi, standalone, ...): standalone
  • any other relevant information:

Question

I use a schema in many place, such as

joi.object().keys({
    user: joi.string().required(),
    pwd: joi.string().required(),
})

but i don't like export it as a variable then import it to use. i want a easy way.

joi.object().userinfo()

I don't know how to implement by joi.extend(). But I use follow code:

joi.object().constructor.prototype['userinfo'] = function(){
    return this.concat({
        user: joi.string().required(),
        pwd: joi.string().required(),
    })
}

so anyone knows a elegance way??

feature

All 12 comments

If you don't want to export the base schema and compose your subschema from it, I would definitely use Joi's extend instead of modifying the prototype of Joi.object.

Check out #1179 for a more detailed discussion of a similar extension.

That being said, since you aren't adding new rules or data coercion or anything, composing with Node honestly will be the easiest...

// base.js
const Joi = require('joi');

module.exports = Joi.object().keys({
    user: joi.string().required(),
    pwd: joi.string().required()
});
// any other file
const BaseSchema = require('./base.js');

const extendedSchema = BaseSchema.keys({ /*additional keys here*/ });
/*
as opposed to using the userInfo extension:
const extendedSchema = Joi.object().keys({ /*additional keys here*/ }).userInfo();
/*

@WesTyler
Thanks, I want to know how to write extension without modify prototype.
I try to this, but no effect. :(

joi.extend({
    name: 'object',
    base: joi.object(),
    rules: [{
        name: 'userinfo',
        setup(param) {
            this.concat({
                user: joi.string().required(),
                pwd: joi.string().required(),
            })
    }]
})

Looks to me like you just want :

Joi.extend({
    name: 'userinfo',
    base: Joi.object().keys({
        user: joi.string().required(),
        pwd: joi.string().required(),
    })
})

@Marsup
you can only write joi.userinfo() as you write, not joi.object().userinfo() that i want to

@XGHeaven I don't understand why it is necessary to write Joi.object().userinfo(). In this case what's happening is that Joi is being extended with a type called userinfo which is itself an object and has the following schema:

Joi.object().keys({
        user: joi.string().required(),
        pwd: joi.string().required(),
    })

@DavidTPate I think userinfo is a decorator for object, like min,max for object.
userinfo decorate a object with user and pwd keys and user is joi.string().required(), pwd is joi.string().required()
So I prefer joi.object().userinfo() not joi.userinfo()...

Gotcha, you can find some additional examples of Joi.extend() here.

It doesn't seem like a very hard one to support, but I don't think it's possible right now. I'll keep this around as a request for when I have time to do it, or anyone feel free to try. It's a small modification of the setup call to use an eventually returned schema value.

I suggest that:

  • if setup return undefined, nothing to do, just like before.
  • if setup return Joi object, to replace schema.
  • if setup return others, throw a error with 'Joi.extend() rule with setup must be return undefined, or Joi object' message.

I'm trying to do this.

My solution has been:

// my common type
const createAudit = Joi.object().keys({
    createdBy: Joi.string()
        .required(),
    createdOn: Joi.date().iso()
        .required()
})
    .and('createdBy', 'createdOn');
// my final type
const schemaFinal = Joi.object({
    id: Joi.string()
        .required(),
    //...
})
    .concat(createAudit)

So now the object as

var o = {
  id: '123',
  createdBy: 'creator'
};

await schemaFinal.validateAsync(o);

would give error where createdOn is required and createdBy and createdOn need to be paired.

Hey @XGHeaven,

How did you get this working with Joi 17.1.1

I am struggling with a similar situation and thought you would be kind enough to help me with this. :)

@katlimruiz your solutions works for me to some extent, where i can extend keys. I also need to add few more keys in the final schema with out modifying base schema.

My base schema is
const ItemSchema = Joi.object({ name: Joi.string().required(), price: Joi.number().required().min(0), description: Joi.string().optional(), variants: Joi.array().items(Joi.object({ name: Joi.string().required(), price: Joi.number().required() }), })

And my final schema would be
const OrderSchema = Joi.object({ cid: Joi.string(), totalQuantity: Joi.number().min(0), itemTotal: Joi.number().min(0), items: Joi.array().items( Joi.object({ quantity: Joi.number().required(), }).concat(ItemSchema) }) };
In this case my final schema would have keys cid, totalQuantity, itemTotal, items array. This items array will have objects matching to ItemSchema, something like

{ "cid": "string", "totalQuantity": 0, "itemTotal": 0, "items": [ { "name": "string", "price": 0, "description": "string", "variants": [ { "name": "string", "price": 0 } ] } ] }
My actual requirement is also to add few more fields like quantity in variants object.
{ "cid": "string", "totalQuantity": 0, "itemTotal": 0, "items": [ { "name": "string", "price": 0, "description": "string", "variants": [ { "name": "string", "price": 0, "quantity": 0 } ] } ] }
So how can we add few more fields inside the nested array in the ItemSchema i.e. base schema. The ItemSchema should be intact and the additional fields should be part of OrderSchema only.

Please let me know the solution if anyone has idea on it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

leore picture leore  路  4Comments

jamesdixon picture jamesdixon  路  4Comments

normancarcamo picture normancarcamo  路  3Comments

PaunPrashant picture PaunPrashant  路  3Comments

Dreamystify picture Dreamystify  路  4Comments