JSON Schemaを利用した外部APIのバリデーションについて
JSON Schemaを利用した外部APIのバリデーションについて
記事を読んでいただきありがとうございます。今回担当のはいのです。
普段はPHPを使用した開発を主にしておりその中で使用したJSON Schemaというものについて紹介します。
背景
- 現在担当しているシステムで外部APIからのレスポンスを整形して利用するといった処理を行なっていた。
- 常に正常なものを受け取れる前提であれば問題ないが、レスポンス形式が変わったする場合は当然あるので対策が必要だった。
- 全てのパラメータを都度チェックするのをphpのみで実装すると冗長的な処理となる為、その対応策としてバリデーションにJSON schemaを利用してみた。
JSON Schemaについて
JSON Schema(JSONスキーマ)は、Json Schema organization によって開発・保守されているスキーマ言語で、JSONデータの構造をJSONそのもので定義するためのもの。
引用元: https://ja.wikipedia.org/wiki/JSON_Schema
例えば以下のような配列があるとする
{
name: "taro",
age: 34,
deleted: false
}
それぞれ name
は文字列、age
は数値型、deleted
はbool型と指定したい場合はJSON Schemaは以下のようにする
{
"type": "object", // オブジェクト型
"properties": {
"name": {
"type": "string" // 文字列型
},
"age": {
"type": "number" // 数値型
}
"deleted": {
"type": "boolean" // bool型
}
}
}
必須にしたい場合はそのパラメータを持つproperties
と同階層にrequired
を定義する
{
"type": "object", // オブジェクト型
"properties": {
"name": {
"type": "string" // 文字列型
},
"age": {
"type": "number" // 数値型
}
"deleted": {
"type": "boolean" // bool型
}
}
"required": [
"name",
"age"
]
}
また複数の型がある場合や、nullを許可する場合は配列で指定することができる
{
"type": "object", // オブジェクト型
"properties": {
"name": {
"type": "string" // 文字列型
},
"age": {
"type": [
"number", // 数値型
"string" // 文字列型
]
}
"deleted": {
"type": [
"boolean", // bool型
"null" // null許容
]
}
}
"required": [
"name",
"age"
]
}
手順
今回は様々なAPIが登録されているRapidAPIを使用した。
RapidAPI
今回はその中からTikTokのデータを取得できるAPIを利用
ScrapTik
https://rapidapi.com/scraptik-api-scraptik-api-default/api/scraptik
まずは使用するAPIのレスポンス形式を確認する。
レスポンス例(TikTokのプロフィール情報を取得)
{
"user": {
"account_type": 0,
"avatar_thumb": {
"uri": "tos-alisg-avt-0068/7324975654267518997",
"url_list": [
"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/tos-alisg-avt-0068/7324975654267518997.webp?lk3s=a5d48078&x-expires=1712156400&x-signature=iZy4hiu76LKQ8pV5LEWMCDYIA3c%3D",
"https://p77-sign-sg.tiktokcdn.com/aweme/100x100/tos-alisg-avt-0068/7324975654267518997.webp?lk3s=a5d48078&x-expires=1712156400&x-signature=VD%2Br59UnLsjmHLF6WeAFqE66LK4%3D",
"https://p16-sign-sg.tiktokcdn.com/aweme/100x100/tos-alisg-avt-0068/7324975654267518997.jpeg?lk3s=a5d48078&x-expires=1712156400&x-signature=bOEf5P%2BYVW2aoAEmaXFEWl%2BxABE%3D"
],
"url_prefix": null
},
"aweme_count": 1204,
"category": "",
"follower_count": 54275490,
"follower_status": 0,
"following_count": 358,
"forward_count": 0,
"ins_id": "bayashi_tv",
"nickname": "バヤシ🥑Bayashi",
"sec_uid": "MS4wLjABAAAANf1ZtZv9Ie4d7jaIkxDcxOnFO-MKuPJmAF_HIQef46WZsFr3bccw-O-ZdgF4-2w5",
"signature": "I like cheese 🧀",
"signature_language": "un",
"total_favorited": 1646850114,
"uid": "6742459250268832769",
"unique_id": "bayashi.tiktok",
}
}
取得したレスポンスをjson schemeのジェネレータを利用し生成する
https://www.liquid-technologies.com/online-json-to-schema-converter
生成結果
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"account_type": {
"type": "integer"
},
"avatar_thumb": {
"type": "object",
"properties": {
"uri": {
"type": "string"
},
"url_list": {
"type": "array",
"items": [
{
"type": "string"
},
{
"type": "string"
},
{
"type": "string"
}
]
},
"url_prefix": {
"type": "null"
}
},
"required": [
"uri",
"url_list",
"url_prefix"
]
},
"aweme_count": {
"type": "integer"
},
"category": {
"type": "string"
},
"follower_count": {
"type": "integer"
},
"follower_status": {
"type": "integer"
},
"following_count": {
"type": "integer"
},
"forward_count": {
"type": "integer"
},
"ins_id": {
"type": "string"
},
"nickname": {
"type": "string"
},
"sec_uid": {
"type": "string"
},
"signature": {
"type": "string"
},
"signature_language": {
"type": "string"
},
"total_favorited": {
"type": "integer"
},
"uid": {
"type": "string"
},
"unique_id": {
"type": "string"
}
},
"required": [
"account_type",
"avatar_thumb",
"aweme_count",
"category",
"follower_count",
"follower_status",
"following_count",
"forward_count",
"ins_id",
"nickname",
"sec_uid",
"signature",
"signature_language",
"total_favorited",
"uid",
"unique_id"
]
}
},
"required": [
"user"
]
}
自動生成後は現在の値を元に型が決められたり全てのパラメータが必須となっている為、必要に応じて編集後ファイルを任意の場所に設置する。
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"aweme_count": {
"type": "integer"
},
"follower_count": {
"type": "integer"
},
"following_count": {
"type": "integer"
},
"nickname": {
"type": "string"
},
"sec_uid": {
"type": "string"
},
"signature": {
"type": "string"
},
"total_favorited": {
"type": "integer"
},
"uid": {
"type": "string"
},
"unique_id": {
"type": "string"
}
},
"required":
"aweme_count",
"follower_count",
"following_count",
"ins_id",
"nickname",
"sec_uid",
"signature",
"total_favorited",
"uid",
"unique_id"
]
}
},
"required": [
"user"
]
}
これをJsonSchemaライブラリで比較する事で、指定したjsonが期待する構造になっているかを判定可能となった。
// APIリクエスト
$this->client = new Client();
$response = $this->client->request(
"GET”,
"https://{$host}{$endpoint}{$buildQuery}",
);
$result = $response->getBody()->getContents();
// JSONSchemaバリデーター生成
$jsonSchemaValidator = new \JsonSchema\Validator();
// JSONSchemaファイル読み込み
$schemaFilePath = "JsonSchemaファイルの絶対パス";
$schema = json_decode(file_get_contents($schemaFilePath), false);
// リクエスト結果jsonデータ
$json = json_decode($result, false);
// バリデーションチェック
$jsonSchemaValidator->validate($json, $schema);
if (!$jsonSchemaValidator->isValid()) {
// エラー時の処理
}
// 後続処理
- jsonデータを扱う場合はバリデーション設定の工数を削減できる
- 作成したJSON Schemaはそのままドキュメントとしても使用できる
- フレームワークやphpバージョン等に依存せずに使用できるのも利点
jsonは様々な言語との親和性も高く、基本知識として持っている人も多いと思うのでその点でも導入コストは低め。
ジェネレータを利用すれば必要な情報を削るのみで、開発経験のあるエンジニアであれば数時間程度と短期間で実装可能なので条件に合う場合は積極的に利用していきたい。
参考サイト
JSON Schemaのすゝめ
https://qiita.com/g0e/items/9a4f886897fd46f107a8
JSON Schemaとは?便利な生成方法をご紹介!
https://apidog.com/jp/blog/what-is-json-schema/
Generator
https://www.liquid-technologies.com/online-json-to-schema-converter