最近拿 JSON schema 來驗證自己的 API 回傳內容有沒有錯誤,在過程中遇到一點小障礙─無法讀取複雜的 JSON schema

假設有支 API /v1/products/{product_id},它的 schema 如下:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Product resource",
    "description": "A product resource",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "data": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "product": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                        "id": {
                            "type": "integer"
                        },
                        "name": {
                            "type": "string"
                        },
                        "price": {
                            "type": "integer"
                        },
                        "created_at": {
                            "type": "integer"
                        },
                        "updated_at": {
                            "type": "integer"
                        }
                    },
                    "required": [
                        "id",
                        "name",
                        "price",
                        "created_at",
                        "updated_at"
                    ]
                }
            }
        }
    },
    "required": [
        "data"
    ]
}

這種簡單的 schema,直接用 json 讀取是沒問題的

import json

with open('specs/product-schema.json', 'r') as schema:
    product_schema = json.loads(schema.read())

但總會有一些意料之外的事

如果今天有些 schema 會一直出現,你不想每個地方都重複寫一樣的東西,那你可能會需要 reference,而 File Reference 就是其中一種,像是:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Product resource",
    "description": "A product resource",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "data": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "product": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                        "id": {
                            "type": "integer"
                        },
                        "name": {
                            "type": "string"
                        },
                        "price": {
                            "type": "integer"
                        },
                        "created_at": {
                            "$ref": "unix-timestamp.json"
                        },
                        "updated_at": {
                            "$ref": "unix-timestamp.json"
                        }
                    },
                    "required": [
                        "id",
                        "name",
                        "price",
                        "created_at",
                        "updated_at"
                    ]
                }
            }
        }
    },
    "required": [
        "data"
    ]
}

created_atupdated_at 原本是 integer,但現在會 refer to unix-timestamp.json

這就無法直接用 json 完整的解析出來了,你可以自己寫一個 RefResolver,或是直接用 jsonref,懶人如我當然就是後者啦 XD

from os.path import json, dirname
import jsonref

base_uri = f'file:{join(dirname(__file__), "specs/")}'
with open('specs/product-schema.json', 'r') as schema:
    product_schema = jsonref.loads(
        schema.read(), base_uri=base_uri, jsonschema=True)

如此一來便能把整個 json 展開來,搭配 jsonschema 做 api validation