kakakakakku blog

Weekly Tech Blog: Keep on Learning!

JSON Schema の dependentRequired で「a を指定する場合は b もセットで必要」を実現する

JSON Schema で dependentRequired を使うと JSON のプロパティ構造を「条件付きで」バリデーションできる🔐 具体例を挙げると,任意プロパティが ab 2つあるときにa を指定する場合は b もセットで必要」というバリデーションをしたいときに使える❗️

json-schema.org

JSON Schema のドキュメント (Conditional schema validation) に載っている例だと,name プロパティは必須で,もし任意の credit_card プロパティを指定する場合は billing_address プロパティもセットで必要という感じ👌

{
  "type": "object",

  "properties": {
    "name": { "type": "string" },
    "credit_card": { "type": "number" },
    "billing_address": { "type": "string" }
  },

  "required": ["name"],

  "dependentRequired": {
    "credit_card": ["billing_address"]
  }
}

動作確認

pytest でテストコードを実装して dependentRequired の動作確認をしてみた❗️ちなみに JSON Schema のバージョンによって記法が違っていて,Draft 2020-12(正確には Draft 2019-09 以降)では dependentRequired で,Draft-07 だと dependencies となる.詳しくは Draft 2019-09 のリリースノートに載っている📝

json-schema.org

テスト項目としては以下の3種類の JSON を JSON Schema Draft 2020-12 と JSON Schema Draft-07 でバリデーションしてみた👌

  • INPUT_1(name)🙆‍♂️
  • INPUT_2(name and credit_card)🙅
  • INPUT_3(name and credit_card and billing_address)🙆‍♂️
import jsonschema
import pytest
from faker import Faker

fake = Faker()


SCHEMA_DRAFT_2020_12 = {
    '$schema': 'https://json-schema.org/draft/2020-12/schema',
    'type': 'object',
    'properties': {
        'name': {'type': 'string'},
        'credit_card': {'type': 'number'},
        'billing_address': {'type': 'string'},
    },
    'required': ['name'],
    'dependentRequired': {'credit_card': ['billing_address']},
}


SCHEMA_DRAFT_07 = {
    '$schema': 'https://json-schema.org/draft-07/schema',
    'type': 'object',
    'properties': {
        'name': {'type': 'string'},
        'credit_card': {'type': 'number'},
        'billing_address': {'type': 'string'},
    },
    'required': ['name'],
    'dependencies': {'credit_card': ['billing_address']},
}


INPUT_1 = {
    'name': fake.name(),
}

INPUT_2 = {
    'name': fake.name(),
    'credit_card': int(fake.credit_card_number()),
}

INPUT_3 = {
    'name': fake.name(),
    'credit_card': int(fake.credit_card_number()),
    'billing_address': fake.address(),
}


def idfn(param):
    return param['id']


@pytest.mark.parametrize(
    'patterns',
    [
        {
            'id': 'Draft 2020-12|1',
            'schema': SCHEMA_DRAFT_2020_12,
            'validator': jsonschema.Draft202012Validator,
            'input': INPUT_1,
            'valid': True,
        },
        {
            'id': 'Draft 2020-12|2',
            'schema': SCHEMA_DRAFT_2020_12,
            'validator': jsonschema.Draft202012Validator,
            'input': INPUT_2,
            'valid': False,
        },
        {
            'id': 'Draft 2020-12|3',
            'schema': SCHEMA_DRAFT_2020_12,
            'validator': jsonschema.Draft202012Validator,
            'input': INPUT_3,
            'valid': True,
        },
        {
            'id': 'Draft-07|1',
            'schema': SCHEMA_DRAFT_07,
            'validator': jsonschema.Draft7Validator,
            'input': INPUT_1,
            'valid': True,
        },
        {
            'id': 'Draft-07|2',
            'schema': SCHEMA_DRAFT_07,
            'validator': jsonschema.Draft7Validator,
            'input': INPUT_2,
            'valid': False,
        },
        {
            'id': 'Draft-07|3',
            'schema': SCHEMA_DRAFT_07,
            'validator': jsonschema.Draft7Validator,
            'input': INPUT_3,
            'valid': True,
        },
    ],
    ids=idfn,
)
def test_json_schema(patterns):
    if patterns['valid']:
        patterns['validator'](patterns['schema']).validate(patterns['input'])
    else:
        with pytest.raises(jsonschema.ValidationError):
            patterns['validator'](patterns['schema']).validate(patterns['input'])

実行すると期待通りになっていた👌

$ uv run pytest --verbose
(中略)
test_main.py::test_json_schema[Draft 2020-12|1] PASSED
test_main.py::test_json_schema[Draft 2020-12|2] PASSED
test_main.py::test_json_schema[Draft 2020-12|3] PASSED
test_main.py::test_json_schema[Draft-07|1] PASSED
test_main.py::test_json_schema[Draft-07|2] PASSED
test_main.py::test_json_schema[Draft-07|3] PASSED

まとめ

JSON Schema の dependentRequired は便利そうだから覚えておこう〜 \( 'ω')/