customcommander
customcommander

Reputation: 18921

JSON Schema / Ajv - How to validate the combined lengths of an array of strings?

Let's say that I need to validate the shipping address of an order:

{ "id": 1
, "recipient": "John Doe"
, "shipping_address": [ "address line 1"
                      , "address line 2"
                      , "address line 3"
                      ]
}

The rules for an address are:

  1. Array of strings
  2. At least one string (and can have as many strings as necessary)
  3. The combined lengths of all strings must not exceed X characters (let's say 50 for the example)

I can implement 1 & 2 with the following JSON Schema:

{ "type": "array"
, "items": { "type": "string" }
, "minItems": 1
}

Question: How do I implement condition 3 with JSON Schema / Ajv?

Upvotes: 0

Views: 3923

Answers (1)

customcommander
customcommander

Reputation: 18921

I found a solution using Ajv custom keywords to design the following schema:

{ "type": "array"
, "items": { "type": "string" }
, "minItems": 1
, "maxCombinedLength": 50
}

The trick is to add support for maxCombinedLength in Ajv:

ajv.addKeyword("maxCombinedLength", {
  validate: (schema, data) => {
    return data.reduce((l, s) => l + s.length, 0) <= schema;
  }
});

Where:

  1. ajv is an instance of Ajv
  2. schema is 50
  3. data is the shipping_address array

DEMO

const validate =
  ajv.compile({ type: 'array'
              , items: { type: 'string' }
              , minItems: 1
              , maxCombinedLength: 50
              });
              
console.log(
  validate([]));
// false (need at least one item)

console.log(
  validate([ "address line 1"
           , "address line 2"
           , "address line 3"
           ]));
// true

console.log(
  validate([ "address line 1"
           , "address line 2"
           , "address line 3"
           , "address line 4"
           , "address line 5"
           , "address line 6"
           , "address line 7"
           , "address line 8"           
           ])
, validate.errors);
// false (maxCombinedLength not satisfied!)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/6.12.0/ajv.min.js"></script>
<script>
const ajv = new Ajv;

ajv.addKeyword("maxCombinedLength", {
  validate: (schema, data, parent_schema) => {
    return data.reduce((l, s) => l + s.length, 0) <= schema;
  }
});

</script>


APPENDIX: How to ignore non-string arrays?

Obviously we can't use maxCombinedLength with an array of objects for example.

Thankfully Ajv allows us to get access to the parent schema:

ajv.addKeyword("maxCombinedLength", {
  validate: (schema, data, parent_schema) => {
    if (parent_schema.items.type === 'string') {
      return data.reduce((l, s) => l + s.length, 0) <= schema;
    } else {
      return true;
    }
  }
});

So with the following schema:

{ "type": "array"
, "items": { "type": "string" }
, "minItems": 1
, "maxCombinedLength": 50
}

The custom keyword function will receive 50 as the schema parameter, the array as the data parameter and the full schema as the parent_schema parameter.

The parent_schema parameter is used to query the type of the array. If we're not expecting strings we can nullify the maxCombinedLength keyword by returning true.

Upvotes: 2

Related Questions