Joi: stripUnknown behaviour for object arrays

Created on 25 Mar 2015  路  8Comments  路  Source: sideway/joi

On v6.0.8, for object arrays, the stripUnknown option does not result in errors when the validation for one of the object keys fails, like the following example:

var Joi = require('joi');

var schema = Joi.array().items(Joi.object().keys({
  one: Joi.string(),
  two: Joi.number()
})).options({ stripUnknown: true });

schema.validate([{ one: 'one', two: 'two' }], function (err, value) {
  console.log('err:', err);
  console.log('value:', value);
});

Is this the expected behaviour or is it a bug? I don't recall this happening on previous versions. If it's a bug, I'll gladly send a pull request.

Thanks!

support

Most helpful comment

Although this issue is closed, I think that there's a huge difference between what stripUnknown _implies_ it does (removing unknown keys from an object) and what it _actually_ does in the context of arrays of objects (removing items from an array that aren't valid per the defined schema). This is very confusing behavior.

If I want to validate an array of objects as part of a larger schema, stripping out unknown keys on the parent object:

var schema = Joi.object().keys({
    dish: Joi.string(),
    ingredients: Joi.array().items(
        Joi.object().keys({
            name: Joi.string(),
            amount: Joi.number(),
        })
    )
})

var data = {
    dish: 'Mac and Cheese',
    ingredients: [{
        name: 'Macaroni',
        amount: 'one'
    }, {
        name: 'Cheese',
        amount: 'two'
    }],
    unnecessaryInfo: 'Blah Blah...'
}

var result = Joi.validate(data, schema, { stripUnknown: true })

What I think is reasonable to expect is for the validation to fail, since the objects passed into the ingredients array don't match the defined schema.

// What I expect to see
// result.error -> [ValidationError: child "ingredients" fails because ["ingredients" at position 0 fails because [child "amount" fails because ["amount" must be a number]]]

Instead, the objects that don't pass validation are stripped out of the array.

result.value = {
    dish: 'Mac and Cheese',
    ingredients: []
}

I think this behavior is very misleading - These fields aren't unknown, they're invalid. I also think it's quite misleading for the behavior of stripping unknown keys from an object to be conflated with stripping mismatching items in an array. There's a very large difference between these two behaviors, and I think it would be much more reasonable to define a property on the array schema definition that determines what behavior Joi ought to take when encountering this kind of situation.

In any case, the workaround is verbose, unclear, and prone to error. One needs to define stripUnkown to false on the array, and true on the member objects:

var schema = Joi.object().keys({
    dish: Joi.string(),
    ingredients: Joi.array().items(
        Joi.object().keys({
            name: Joi.string(),
            amount: Joi.number(),
        }).options({stripUnknown: true})
    ).options({stripUnknown: false})
})

var result = Joi.validate(data, schema, { stripUnknown: true })

All 8 comments

This is the expected behavior. The release notes explained that, it seems the documentation doesn't. It seems like the logical thing to do but I'd accept a PR to clarify that.

I think it is a little bit misleading, because for this particular case, we are not talking about unknown object keys but invalid values for known keys.

Anyway, my issue is that I'm setting a global value for the option on hapi, but I guess I can just override it for the specific cases where I want strict validation.

They are not invalid if you use that option, stripUnkown has the same behavior as on objects.

Yes, I know, I'm just saying that maybe stripUnknown is not the best name for what it does (in this particular case, at least).

Although this issue is closed, I think that there's a huge difference between what stripUnknown _implies_ it does (removing unknown keys from an object) and what it _actually_ does in the context of arrays of objects (removing items from an array that aren't valid per the defined schema). This is very confusing behavior.

If I want to validate an array of objects as part of a larger schema, stripping out unknown keys on the parent object:

var schema = Joi.object().keys({
    dish: Joi.string(),
    ingredients: Joi.array().items(
        Joi.object().keys({
            name: Joi.string(),
            amount: Joi.number(),
        })
    )
})

var data = {
    dish: 'Mac and Cheese',
    ingredients: [{
        name: 'Macaroni',
        amount: 'one'
    }, {
        name: 'Cheese',
        amount: 'two'
    }],
    unnecessaryInfo: 'Blah Blah...'
}

var result = Joi.validate(data, schema, { stripUnknown: true })

What I think is reasonable to expect is for the validation to fail, since the objects passed into the ingredients array don't match the defined schema.

// What I expect to see
// result.error -> [ValidationError: child "ingredients" fails because ["ingredients" at position 0 fails because [child "amount" fails because ["amount" must be a number]]]

Instead, the objects that don't pass validation are stripped out of the array.

result.value = {
    dish: 'Mac and Cheese',
    ingredients: []
}

I think this behavior is very misleading - These fields aren't unknown, they're invalid. I also think it's quite misleading for the behavior of stripping unknown keys from an object to be conflated with stripping mismatching items in an array. There's a very large difference between these two behaviors, and I think it would be much more reasonable to define a property on the array schema definition that determines what behavior Joi ought to take when encountering this kind of situation.

In any case, the workaround is verbose, unclear, and prone to error. One needs to define stripUnkown to false on the array, and true on the member objects:

var schema = Joi.object().keys({
    dish: Joi.string(),
    ingredients: Joi.array().items(
        Joi.object().keys({
            name: Joi.string(),
            amount: Joi.number(),
        }).options({stripUnknown: true})
    ).options({stripUnknown: false})
})

var result = Joi.validate(data, schema, { stripUnknown: true })

Agree to disagree. Stripping unknown keys from an object isn't that different from stripping unknown values from an array. Though a separate flag wouldn't hurt I guess. Can you create a new issue to track it ?

The confusing part is the adjective, not the verb. In one case you are in fact stripping unknown (keys). In the array-of-schema scenario, you're really stripping _invalid_ (values). No array index is unknown or outside the schema expecting that array, unless by virtue of bounds on the size of a non-sparse array.

To me it would make a lot of sense to handle these behaviors separately with unique methods, stripUnknown and stripInvalid, with stripInvalid working on values in arrays and objects, but stripUnknown only working on keys of objects.

It's already a solved problem.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kailashyogeshwar85 picture kailashyogeshwar85  路  4Comments

mohamadresaaa picture mohamadresaaa  路  3Comments

sergibondarenko picture sergibondarenko  路  3Comments

chrisegner picture chrisegner  路  4Comments

REBELinBLUE picture REBELinBLUE  路  3Comments