tok
tok

Reputation: 981

Dynamic value for maxItems in JSON Schema

I'm trying to set a value for maxItems based on another value in JSON-data. Label is 'yyyy_mm' and maxItems should be the number of days in month 'mm' in year 'yyyy' (leap year or not). This is what I have so far.

The schema:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "period": {
            "type": "object",
            "properties": {
                "label": {
                    "type": "string",
                    "pattern": "^\\d{4}_(0[1-9]|1[0-2])$"
                }
            },
            "required": [
                "label"
            ]
        },
        "days": {
            "type": "array",
            "items": {
                "type": "integer"
            },
            "if": {
                "properties": {
                    "period": {
                        "properties": {
                            "label": {
                                "pattern": "^\\d{4}_(01|03|05|07|08|10|12)$"
                            }
                        }
                    }
                }
            },
            "then": {
                "maxItems": 31
            },
            "else": {
                "if": {
                    "properties": {
                        "period": {
                            "properties": {
                                "label": {
                                    "pattern": "^\\d{4}_(04|06|09|11)$"
                                }
                            }
                        }
                    }
                },
                "then": {
                    "maxItems": 30
                },
                "else": {
                    "if": {
                        "properties": {
                            "period": {
                                "properties": {
                                    "label": {
                                        "pattern": "^\\d{4}_02$"
                                    }
                                }
                            }
                        }
                    },
                    "then": {
                        "if": {
                            "properties": {
                                "period": {
                                    "properties": {
                                        "label": {
                                            "pattern": "^(\\d{2}([02468][048]|[13579][26])|(\\d{2}([02468][1235679]|[13579][01345789])))_02$"
                                        }
                                    }
                                }
                            }
                        },
                        "then": {
                            "maxItems": 29
                        },
                        "else": {
                            "maxItems": 28
                        }
                    }
                }
            }
        }
    },
    "required": [
        "period"
    ],
    "additionalProperties": false
}

Example data (this should not validate):

{
  "period": {
    "label": "2024_02"
  },
  "days": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
}

I've tested this using JSON Schema Validator https://www.jsonschemavalidator.net/ and it always validates regardless of the value of label. What is wrong here and how to correct it?

Upvotes: 0

Views: 29

Answers (1)

Jeremy Fiel
Jeremy Fiel

Reputation: 3254

You were fairly close, but the if, then, must be defined at the highest possible level to drill into each subschema. In other words, {"if": { "properties": {"period"... cannot evaluate the property period within the days sibling schema.

If you have more than one if, then condition, it's advised to wrap them in an allOf to make sure they are all evaluated

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "period": {
            "type": "object",
            "properties": {
                "label": {
                    "type": "string"
                }
            },
            "required": [
                "label"
            ]
        },
        "days": {
            "type": "array",
            "items": {
                "type": "integer"
            }
        }
    },
    "allOf": [
        {
            "$ref": "#/definitions/label-pattern-31dayMonths"
        },
        {
            "$ref": "#/definitions/label-pattern-30dayMonths"
        },
        {
            "$ref": "#/definitions/label-pattern-february"
        }
    ],
    "required": [
        "period"
    ],
    "additionalProperties": false,
    "definitions": {
        "label-pattern-31dayMonths": {
            "if": {
                "properties": {
                    "period": {
                        "properties": {
                            "label": {
                                "pattern": "^\\d{4}_(01|03|05|07|08|10|12)$"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "days": {
                        "maxItems": 31
                    }
                }
            }
        },
        "label-pattern-30dayMonths": {
            "if": {
                "properties": {
                    "period": {
                        "properties": {
                            "label": {
                                "pattern": "^\\d{4}_(04|06|09|11)$"
                            }
                        }
                    }
                }
            },
            "then": {
                "properties": {
                    "days": {
                        "maxItems": 30
                    }
                }
            }
        },
        "label-pattern-february": {
            "if": {
                "properties": {
                    "period": {
                        "properties": {
                            "label": {
                                "pattern": "^\\d{4}_02$"
                            }
                        }
                    }
                }
            },
            "then": {
                "if": {
                    "properties": {
                        "period": {
                            "properties": {
                                "label": {
                                    "pattern": "^(\\d{2}([02468][048]|[13579][26])|(\\d{2}([02468][1235679]|[13579][01345789])))_02$"
                                }
                            }
                        }
                    }
                },
                "then": {
                    "properties": {
                        "days": {
                            "maxItems": 29
                        }
                    }
                },
                "else": {
                    "properties": {
                        "days": {
                            "maxItems": 28
                        }
                    }
                }
            }
        }
    }
}

This fails as expected, with more than the maxItems allowed.

{
  "period": {
    "label": "2024_02"
  },
  "days": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
}

If you remove the last two entries, the schema passes after matching the february conditional statement then condition.

p.s. I made up the schema naming in definitions just to clean up the readability

Upvotes: 1

Related Questions