'Joi validations - if this and that, then. Else

I'm trying to apply multiple validations to a Joi schema that checks the value of two keys and validates a final key based on the result.

Using the example body of below, I want to check the field of channel. This could be either LOCAL or GLOBAL.

{
    "channel":"LOCAL",
    "beneficiary": {
        "currency": "GBP",
        "accountNumber": "12345678"
    }
}

If LOCAL and the beneficiary.currency is GBP, then the following should apply: Joi.string().regex(/^[0-9]{8}$/).required()

If GLOBAL and the beneficiary.currency is also GBP, then the following should apply: Joi.string().regex(/^[A-Z]{2}[0-9]{2}([0-9]|[A-Z])+([0-9A-Z]+){10,31}$/).required()

However this needs to work for a matrix of many possibilities as per the following +n amount of supported currencies.

TYPE CURRENCY REGEX
GLOBAL USD ^[A-Z]{2}[0-9]{2}([0-9]|[A-Z])+([0-9A-Z]+){10,31}$
LOCAL USD ^[0-9A-Z]{1,45}$
GLOBAL GBP ^[A-Z]{2}[0-9]{2}([0-9]|[A-Z])+([0-9A-Z]+){10,31}$
LOCAL GBP ^[0-9]{8}$
GLOBAL AUD ^[A-Za-z0-9]{0,9}$
LOCAL AUD ^[0-9]{1,7}$
GLOBAL SGD ^[A-Za-z0-9]+$
LOCAL SGD ^[A-Za-z0-9]{1,50}$

I've found I can use the following but doesn't scale across the matrix of possible options:

     accountNumber: Joi.string().when(Joi.ref('...channel', {
       is: 'LOCAL',
       then: Joi.when(Joi.ref('currency'), { is: 'GBP', then: Joi.string().regex(/^[0-9]{8}$/).required() })
     })


Solution 1:[1]

I would use oneOf for this use case:

{
  "type": "object",
  "$schema": "http://json-schema.org/draft-06/schema#",
  "description": "JSON schema generated with JSONBuddy https://www.json-buddy.com",
  "oneOf": [
    {
      "properties": {
        "channel": {
          "type": "string",
          "const": "GLOBAL"
        },
        "beneficiary": {
          "$ref": "#/definitions/beneficiary-GLOBAL-USD"
        }
      }
    },
    {
      "properties": {
        "channel": {
          "type": "string",
          "const": "LOCAL"
        },
        "beneficiary": {
          "$ref": "#/definitions/beneficiary-LOCAL-USD"
        }
      }
    }    
  ],
  "definitions": {
    "beneficiary-GLOBAL-USD": {
      "type": "object",
      "properties": {
        "accountNumber": {
          "pattern": "^[A-Z]{2}[0-9]{2}([0-9]|[A-Z])+([0-9A-Z]+){10,31}$"
        },
        "currency": {
          "const": "USD"
        }
      }
    },
    "beneficiary-LOCAL-USD": {
      "type": "object",
      "properties": {
        "accountNumber": {
          "pattern": "^[0-9A-Z]{1,45}$"
        },
        "currency": {
          "const": "USD"
        }
      }
    }    
  }
}

If you also put the single sub-schemas as $ref in separate definitions is up to you. But I am afraid you can't get much less verbose.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Clemens