TECH BLOG
エクスクリエ
エクスクリエ

JSON Schemaを利用した外部APIのバリデーションについて

Cover Image for 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

    https://rapidapi.com/hub

    今回はその中から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

    私たちは積極的に採用活動をしております。
    https://www.excrie.co.jp/recruit/

    Companies

    エクスクリエ
    クロス・マーケティンググループ
    メタサイト
    クロス・コミュニケーション

    Tags