76

I am getting an array of objects to backend, where each object contains a service name. The structure looks like below

[{"serviceName":"service1"},
{"serviceName":"service2"},..]

when I get the array at backend, I want to validate that every object in the array has serviceName property.

I had written the following code, but even though I pass valid array, I am getting validation error.

var Joi = require('joi');
var service = Joi.object().keys({
  serviceName: Joi.string().required()
});

var services = Joi.array().ordered(service);

var test = Joi.validate([{serviceName:'service1'},{serviceName:'service2'}],services)

For the above code, I am always getting the validation error with message

"value" at position 1 fails because array must contain at most 1 items
Marecky
  • 1,924
  • 2
  • 25
  • 39
zakir
  • 2,390
  • 2
  • 13
  • 16

6 Answers6

143

replacing ordered with items will work.

let Joi = require('joi')
let service = Joi.object().keys({
  serviceName: Joi.string().required(),
})

let services = Joi.array().items(service)

let test = Joi.validate(
  [{ serviceName: 'service1' }, { serviceName: 'service2' }],
  services,
)

For reference click here

Sagar
  • 4,473
  • 3
  • 32
  • 37
zakir
  • 2,390
  • 2
  • 13
  • 16
  • Exactly what I needed. Thank you! – Yuschick Mar 15 '18 at 06:35
  • 6
    `Joi.validate` is deprecated as of version 16. To keep the same solution all you have to do now is instead of `Joi.validate([], services)`, you do `services.validate([], callback)` or `services.validateAsync([])` – Jeffery ThaGintoki Dec 01 '19 at 00:18
  • 2
    It will pass the validation for empty array `service = []` use `Joi.array().min(1).items(service)` to make sure service contains some value. – sujeet Oct 26 '20 at 13:18
  • In-fact .key() function is also not required now, this will also work. let service = Joi.object({ serviceName: Joi.string().required(), }); – Ahmer Saeed Mar 23 '21 at 05:52
30

A basic/ clearer example is as follows. To validate a JSON request like this:

   {
    "data": [
            {
        "keyword":"test",
        "country_code":"de",
        "language":"de",
        "depth":1
            }
        ]
    }

Here is the Joi validation:

 seoPostBody: {
    body: {
      data: Joi.array()
        .items({
          keyword: Joi.string()
            .required(),
          country_code: Joi.string()
            .required(),
          language: Joi.string()
            .required(),
          depth: Joi.number()
            .required(),
        }),
    },
  };

This is what I am doing in NodeJs, might need some slight changes for other platforms

sediq khan
  • 515
  • 4
  • 10
  • thanks, you example helped me in my use case; json ```json "tags": [ "61b394ead5a3863d6a41fedf", "61b292f38561f0c5550ca78c" ] ``` validation ```js tags: Joi.array().max(4).min(1).items(Joi.string()) ``` – Cuado Dec 11 '21 at 12:39
8
const test = {
body: Joi.array()
    .items({
        x: Joi.string().required(),
        y: Joi.string().required(),
        z: Joi.string().required(),
        date: Joi.string().required(),
    })
};
rajiv patel
  • 319
  • 3
  • 4
  • 3
    Welcome to Stack Overflow. Code-only answers are discouraged on Stack Overflow because they don't explain how it solves the problem. Please edit your answer to explain what this code does and how it improves on the existing upvoted answers this question already has, so that it is useful to other users with similar issues. – FluffyKitten Sep 17 '20 at 08:42
3

Just want to make it more clear. I'm currently using "@hapi/joi:16.1.7".

Let's say you want your schema to validate this array of objects.

const example = [
   {
      "foo": "bar",
      "num": 1,
      "is_active": true,
   }
];

Then schema's rules should be:

var validator = require('@hapi/joi');

const rules = validator.array().items(
    validator.object(
        foo: validator.string().required(),
        num: validator.number().required(),
        is_active: validator.boolean().required(),
    ),
);

const { error } = rules.validate(example);
Him Hah
  • 1,385
  • 14
  • 9
2

For Joi you can use below which is working fine for me, this will validate that array must have at-least on object with key serviceName-

const Joi = require('joi');
const itemsArray = Joi.array().items(
            Joi.object({
                serviceName: Joi.string().required(),
            })
        ).min(1).required();

        const itemSchema = Joi.array().items(itemsArray).when('checkout_type', {
            is: 'guest',
            then: Joi.array().required(),
        }).required();

let schema = Joi.object().keys({
        items: Joi.alternatives().try(itemsArray, itemSchema).required(),
    });
PrashantAjani
  • 376
  • 3
  • 6
  • What if I want to match one of the schemas and if that matches the object ignore another schema? https://stackoverflow.com/questions/74658952/joi-validations-if-object-matches-the-schema-validate-against-it-from-multiple – Bravo Dec 02 '22 at 18:01
0

Libraries like these are great but wouldn’t it be even better if we could use them in a more seamless way, like in a Request pipeline? Let’s have a look firstly how we would use Joi in an Express app in Node.js:

const Joi = require('joi'); 
app.post('/blog', async (req, res, next) => { 
  const { body } = req; const 
  blogSchema = Joi.object().keys({ 
    title: Joi.string().required 
    description: Joi.string().required(), 
    authorId: Joi.number().required() 
  }); 
  const result = Joi.validate(body, blogShema); 
  const { value, error } = result; 
  const valid = error == null; 
  if (!valid) { 
    res.status(422).json({ 
      message: 'Invalid request', 
      data: body 
    }) 
  } else { 
    const createdPost = await api.createPost(data); 
    res.json({ message: 'Resource created', data: createdPost }) 
  } 
});

The above works. But we have to, for each route:

  1. create a schema
  2. call validate()

It’s, for lack of better word, lacking in elegance. We want something slick looking

Building a middleware

Let’s see if we can’t rebuild it a bit to a middleware. Middlewares in Express is simply something we can stick into the request pipeline whenever we need it. In our case we would want to try and verify our request and early on determine whether it is worth proceeding with it or abort it.

So let’s look at a middleware. It’s just a function right:

const handler = (req, res, next) = { // handle our request } 
const middleware = (req, res, next) => { // to be defined } 
app.post( '/blog', middleware, handler )

It would be neat if we could provide a schema to our middleware so all we had to do in the middleware function was something like this:

(req, res, next) => { 
  const result = Joi.validate(schema, data) 
}

We could create a module with a factory function and module for all our schemas. Let’s have a look at our factory function module first:

const Joi = require('joi'); 
const middleware = (schema, property) => { 
  return (req, res, next) => { 
  const { error } = Joi.validate(req.body, schema); 
  const valid = error == null; 
  
  if (valid) { 
    next(); 
  } else { 
    const { details } = error; 
    const message = details.map(i => i.message).join(',');
 
    console.log("error", message); 
   res.status(422).json({ error: message }) } 
  } 
} 
module.exports = middleware;

Let’s thereafter create a module for all our schemas, like so:

// schemas.js 
const Joi = require('joi') 
const schemas = { 
  blogPOST: Joi.object().keys({ 
    title: Joi.string().required 
    description: Joi.string().required() 
  }) 
  // define all the other schemas below 
}; 
module.exports = schemas;

Ok then, let’s head back to our application file:

// app.js 
const express = require('express') 
const cors = require('cors'); 
const app = express() 
const port = 3000 
const schemas = require('./schemas'); 
const middleware = require('./middleware'); 
var bodyParser = require("body-parser"); 

app.use(cors()); 
app.use(bodyParser.json()); 
app.get('/', (req, res) => res.send('Hello World!')) 
app.post('/blog', middleware(schemas.blogPOST) , (req, res) => { 
  console.log('/update'); 
  res.json(req.body); 
}); 
 app.listen(port, () => console.log(`Example app listening on port ${port}!`))
Omar Saade
  • 320
  • 2
  • 8