Joi: convert option doesn't work for joi.string() when value is a number

Created on 20 Feb 2018  路  9Comments  路  Source: sideway/joi

Context

  • node version: v6.11.0
  • joi version: 10.6 - 13.1.2
  • used with: standalone

What are you trying to achieve or the steps to reproduce ?

trying to use the convert validation option to cast a number to a string

Which result you had ?

this works ( casting String to Number ):

joi.validate( "1", joi.number(), { convert : yes } )

while the following returns an error: ValidationError: "value" must be a string

joi.validate( 1, joi.string(), { convert : yes } )

What did you expect ?

I would expect the second example to cast the number into string and not return an error

feature

Most helpful comment

This was discussed in other issues. I used this workaround today :

const JoiStringConvertible = function (joi) {
  return {
    base: joi.string(),
    name: 'stringConvertible',
    coerce (value, state, options) {
      function isNumeric (n) {
        return !isNaN(parseFloat(n)) && isFinite(n)
      }
      if (isNumeric(value)) { return value.toString() }
      return value
    }
  }
}

const Joi = require('joi').extend([ JoiStringConvertible ])
const schema = Joi.object().keys({
   id: Joi.stringConvertible().optional().allow(null).empty('')
})

// ===
Joi.validate({ id: 123 }, schema) // => ok { id: "123" }
Joi.validate({ id: "abc" }, schema)  // => ok { id: "abc" }

Joi is a nice extensible piece of code, thank you

All 9 comments

Yeah, this is how it should work according to the docs. In reality, the option only converts from strings.

I suspect the immediate fix, is to update the docs.

If you really need the second form, a new .stringify() option could probably be added to the string type, which will then convert the input to a string.

For reference it's already been discussed in https://github.com/hapijs/joi/issues/857. I'm not very fond of reusing the convert flag for this since it's the default, maybe with a new method like you suggested.

thanks, it does seem like reusing convert for this might not be the best idea. stringify() sounds good. Some suggestions for the api:

  1. stringify() without arguments should probably be disallowed since the only reasonable default would be to convert anything using .toString(), which could lead to many unexpected results
  2. stringify( func ) - would use the provided function for stringification ( the above behaviour could be easily recovered if someone really knows what they are doing and\or is willing to take the risk )
  3. stringify( joiSchema ) - would only stringify ( using .toString() ) if then value matches given schema. Example: joi.string().stringify( joi.number() ) would provide what is asked for in this issue and #857.
  4. stringify([ joiSchema, ... ]) - as above, but matches agains any of the provided schemas
  5. stringify({ schema: s, func: f }) - matches against schema and strigifies using func
  6. stringify([{ schema: s, func: f }, ... ]) - as above, but uses func of the first schema to match

in cases 5 and 6, schema could be an array of schemas, which would allow to match against multiple schemas and use one common stratification function. Although the same would be achievable with alternatives ( following the same argument case 4 seems redundant as well ).

I'm inclined to agree with it being an odd usecase. Would you care to share why you need this? Couldn't it be solved with alternatives()?

This was discussed in other issues. I used this workaround today :

const JoiStringConvertible = function (joi) {
  return {
    base: joi.string(),
    name: 'stringConvertible',
    coerce (value, state, options) {
      function isNumeric (n) {
        return !isNaN(parseFloat(n)) && isFinite(n)
      }
      if (isNumeric(value)) { return value.toString() }
      return value
    }
  }
}

const Joi = require('joi').extend([ JoiStringConvertible ])
const schema = Joi.object().keys({
   id: Joi.stringConvertible().optional().allow(null).empty('')
})

// ===
Joi.validate({ id: 123 }, schema) // => ok { id: "123" }
Joi.validate({ id: "abc" }, schema)  // => ok { id: "abc" }

Joi is a nice extensible piece of code, thank you

@jeremiegirault thanks for the snippet. worked beautifully. thanks to the creators for making Joi so extensible

I think it's pretty clear we won't do it now, closing.

const JoiStringConvertible = function (joi) {
  return {
    base: joi.string(),
    name: 'stringConvertible',
    coerce (value, state, options) {
      function isNumeric (n) {
        return !isNaN(parseFloat(n)) && isFinite(n)
      }
      if (isNumeric(value)) { return value.toString() }
      return value
    }
  }
}

const Joi = require('joi').extend([ JoiStringConvertible ])
const schema = Joi.object().keys({
   id: Joi.stringConvertible().optional().allow(null).empty('')
})

// ===
Joi.validate({ id: 123 }, schema) // => ok { id: "123" }
Joi.validate({ id: "abc" }, schema)  // => ok { id: "abc" }

doesn't work with 19.x

how to update!?

`The following will work for you

result = Joi.extend({
type: 'string',
base: Joi.string(),
coerce: {
from: 'number',
method(value, helpers) {
if (!isNaN(parseFloat(value)) && isFinite(value)) {
return { value: value.toString() }
}
return { value }
}
}
});
result = result.string();`

Was this page helpful?
0 / 5 - 0 ratings