Python 中 jsonschema 深度運(yùn)用
前言
jsonschema 是一個(gè)強(qiáng)大的庫(kù),用于驗(yàn)證 JSON 數(shù)據(jù)是否符合特定的模式(Schema)。在實(shí)際應(yīng)用中,你可能會(huì)遇到更復(fù)雜的 JSON 結(jié)構(gòu)和驗(yàn)證需求。下面我將詳細(xì)介紹 jsonschema 的一些高級(jí)用法,包括復(fù)雜的數(shù)據(jù)結(jié)構(gòu)、自定義驗(yàn)證器以及如何處理嵌套的 JSON 對(duì)象。
1. 復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
1.1 數(shù)組驗(yàn)證
你可以驗(yàn)證數(shù)組中的每個(gè)元素是否符合特定的模式。
import json
from jsonschema import validate, ValidationError
# 定義 JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"ages": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 120
}
}
},
"required": ["name", "ages"]
}
# 待驗(yàn)證的 JSON 數(shù)據(jù)
data = {
"name": "Alice",
"ages": [25, 30, 35]
}
# 驗(yàn)證 JSON 數(shù)據(jù)
try:
validate(instance=data, schema=schema)
print("JSON data is valid.")
except ValidationError as e:
print(f"JSON data is invalid: {e}")
1.2 嵌套對(duì)象驗(yàn)證
你可以驗(yàn)證嵌套的對(duì)象是否符合特定的模式。
# 定義 JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"zipcode": {"type": "string"}
},
"required": ["street", "city"]
}
},
"required": ["name", "address"]
}
# 待驗(yàn)證的 JSON 數(shù)據(jù)
data = {
"name": "Alice",
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": "12345"
}
}
# 驗(yàn)證 JSON 數(shù)據(jù)
try:
validate(instance=data, schema=schema)
print("JSON data is valid.")
except ValidationError as e:
print(f"JSON data is invalid: {e}")
2. 自定義驗(yàn)證器
有時(shí)候你需要進(jìn)行更復(fù)雜的驗(yàn)證邏輯,這時(shí)可以使用 jsonschema 的 Validator 類(lèi)來(lái)創(chuàng)建自定義驗(yàn)證器。
2.1 自定義格式驗(yàn)證
你可以添加自定義的格式驗(yàn)證器,例如驗(yàn)證電話(huà)號(hào)碼格式。
from jsonschema import Draft7Validator, FormatChecker, ValidationError
# 定義自定義格式驗(yàn)證器
def is_valid_phone_number(phone_number):
return phone_number.isdigit() and len(phone_number) == 10
format_checker = FormatChecker()
format_checker.checks('phone')(is_valid_phone_number)
# 定義 JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"phone": {"type": "string", "format": "phone"}
},
"required": ["name", "phone"]
}
# 待驗(yàn)證的 JSON 數(shù)據(jù)
data = {
"name": "Alice",
"phone": "1234567890"
}
# 創(chuàng)建 Validator 并驗(yàn)證 JSON 數(shù)據(jù)
validator = Draft7Validator(schema, format_checker=format_checker)
try:
validator.validate(data)
print("JSON data is valid.")
except ValidationError as e:
print(f"JSON data is invalid: {e}")
2.2 自定義驗(yàn)證函數(shù)
你可以為特定屬性添加自定義的驗(yàn)證邏輯。
from jsonschema import Draft7Validator, ValidationError
# 定義自定義驗(yàn)證函數(shù)
def is_even(value):
if value % 2 != 0:
raise ValidationError(f"{value} is not an even number")
# 定義 JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"number": {"type": "integer"}
},
"required": ["name", "number"],
"additionalProperties": False
}
# 創(chuàng)建 Validator 并添加自定義驗(yàn)證函數(shù)
validator = Draft7Validator(schema)
validator.VALIDATORS["even"] = is_even
schema["properties"]["number"]["even"] = True
# 待驗(yàn)證的 JSON 數(shù)據(jù)
data = {
"name": "Alice",
"number": 4
}
# 驗(yàn)證 JSON 數(shù)據(jù)
try:
validator.validate(data)
print("JSON data is valid.")
except ValidationError as e:
print(f"JSON data is invalid: {e}")
3. 使用 RefResolver 進(jìn)行模塊化驗(yàn)證
對(duì)于大型項(xiàng)目,你可能希望將 Schema 分成多個(gè)文件,并使用引用($ref)來(lái)組合它們。RefResolver 可以幫助你管理這些引用。
3.1 模塊化 Schema 文件
假設(shè)你有兩個(gè)文件:person_schema.json 和 address_schema.json。
person_schema.json
{
"$id": "http://example.com/schemas/person",
"type": "object",
"properties": {
"name": {"type": "string"},
"address": {"$ref": "http://example.com/schemas/address"}
},
"required": ["name", "address"]
}
address_schema.json
{
"$id": "http://example.com/schemas/address",
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"zipcode": {"type": "string"}
},
"required": ["street", "city"]
}
3.2 使用 RefResolver 進(jìn)行驗(yàn)證
import json
from jsonschema import RefResolver, Draft7Validator, ValidationError
# 讀取 Schema 文件
with open('person_schema.json') as f:
person_schema = json.load(f)
with open('address_schema.json') as f:
address_schema = json.load(f)
# 創(chuàng)建 RefResolver
resolver = RefResolver(
base_uri="http://example.com/schemas/",
referrer=person_schema,
store={
"http://example.com/schemas/person": person_schema,
"http://example.com/schemas/address": address_schema
}
)
# 創(chuàng)建 Validator
validator = Draft7Validator(person_schema, resolver=resolver)
# 待驗(yàn)證的 JSON 數(shù)據(jù)
data = {
"name": "Alice",
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": "12345"
}
}
# 驗(yàn)證 JSON 數(shù)據(jù)
try:
validator.validate(data)
print("JSON data is valid.")
except ValidationError as e:
print(f"JSON data is invalid: {e}")
4. 使用 jsonschema 進(jìn)行數(shù)據(jù)轉(zhuǎn)換
雖然 jsonschema 主要用于驗(yàn)證,但你可以結(jié)合其他庫(kù)(如 voluptuous 或 marshmallow)來(lái)進(jìn)行數(shù)據(jù)轉(zhuǎn)換和驗(yàn)證。
4.1 結(jié)合 voluptuous 進(jìn)行數(shù)據(jù)轉(zhuǎn)換
voluptuous 是一個(gè)輕量級(jí)的數(shù)據(jù)驗(yàn)證和轉(zhuǎn)換庫(kù),可以與 jsonschema 結(jié)合使用。
import json
import voluptuous as vol
from jsonschema import validate, ValidationError
# 定義 JSON Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"email": {"type": "string", "format": "email"}
},
"required": ["name", "age"]
}
# 定義 Voluptuous Schema
vol_schema = vol.Schema({
vol.Required('name'): str,
vol.Required('age'): int,
vol.Optional('email'): vol.Email()
})
# 待驗(yàn)證的 JSON 數(shù)據(jù)
data = {
"name": "Alice",
"age": 30,
"email": "alice@example.com"
}
# 驗(yàn)證 JSON 數(shù)據(jù)
try:
validate(instance=data, schema=schema)
transformed_data = vol_schema(data)
print("JSON data is valid and transformed:", transformed_data)
except (ValidationError, vol.Invalid) as e:
print(f"JSON data is invalid: {e}")