jonasschmidt / ex_json_schema

An Elixir JSON Schema validator
MIT License
366 stars 98 forks source link

Support custom formats for non-string types #65

Closed stefanchrobot closed 3 years ago

stefanchrobot commented 3 years ago

Hi, would you be open to accepting a PR that allows using custom format for non-string types? To the best of my knowledge, this would be a valid non-standard extension. Please let me know if this is a bad idea.

We're specifically looking for enabling custom format support for numbers/integers. We're working with APIs that return timestamps in different formats, including a float with 3 decimal points of precision. We're considering using the format field to describe that the value has a very specific interpretation, but it would be great to be able to validate it with ExJsonSchema.Validator.validate. While a lot can be expressed with built-in options, running custom validation on any data gives the comfort of 100% flexibility.

An example schema would look like this:

{
  "type": "object",
  "properties": {
    "foo": {
      "type": "integer",
      "format": "integer-even"
    }
  }
}

The format option is ignored for non-strings (test).

I'd be happy to contribute a PR:

I think the only thing to decide would be the behaviour of built-in formats on non-strings. My take is that they should still be ignored and only custom formats should be allowed.

stefanchrobot commented 3 years ago

Or maybe instead we should think about a more generic approach for plugging in custom validations?

jonasschmidt commented 3 years ago

Hey, in theory what you're trying to do could be achieved using "multipleOf": 0.001 as described here: https://stackoverflow.com/questions/27260587/decimal-precision-in-json-schema

Unfortunately we're dealing with floats here with unknown precision. A lot of JSON Schema libs have trouble with this for the same reasons. So for example:

iex(18)> ExJsonSchema.Validator.valid?(%{"type" => "number", "multipleOf" => 0.01}, 147.41)
true
iex(19)> ExJsonSchema.Validator.valid?(%{"type" => "number", "multipleOf" => 0.01}, 147.42)
false

Maybe you can have a look at the multipleOf implementation first and try to make it work with reasonable precision? It can never be entirely accurate unless we use a proper decimal representation, but maybe we can make it Good Enough™️ with floats?

stefanchrobot commented 3 years ago

I think it makes sense to use decimal to implement this, otherwise this is going to be an everlasting cat and mouse game. Opened #66 for that.

jonasschmidt commented 3 years ago

Yes, good call 👍

stefanchrobot commented 3 years ago

Thanks for accepting the PR! I think this solves our immediate needs.