every Tech Blog

株式会社エブリーのTech Blogです。

OpenAPI における null 値の表現の仕方

こんにちは、@きょーです!普段はデリッシュキッチン開発部のバックエンド中心で業務をしています。

はじめに

OpenAPI で API 仕様書を書く際、null 値を許容するプロパティの表現方法はバージョンによって異なります。たとえば、ユーザープロフィールのメールアドレスのように「値が存在しない(null)」を許容したいケースはよくありますが、その書き方や推奨される方法は OpenAPI のバージョンごとに変化してきました。

この記事では、OpenAPI 3.0.0 と 3.1.0 それぞれでの null 許容プロパティの書き方や、その背景、なぜ仕様が変わったのか、どちらを使うべきかについて解説します。API 設計やスキーマ管理で迷ったときの参考になれば幸いです。

OpenAPI 3.0.0 での nullable の基本的な使い方

OpenAPI 3.0.0 ではnullable: trueを使用することで、プロパティが null 値を許容することを表現できます。

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        email:
          type: string
          nullable: true # emailはnull値を許容

swagger で表示すると以下のようになります。

OpenAPI 3.1.0 での nullable の表現方法

OpenAPI 3.1.0 では、nullableキーワードが廃止され、代わりにtype配列にnullを含める形で表現するようになりました。

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        email:
          type: [string, "null"] # emailはnull値を許容

swagger で表示すると以下のようになります。

なぜ変更されたのか

OpenAPI 3.1.0 でのnullableの廃止には、重要な背景があります。この変更はOpenAPI Specification の Proposalで詳しく議論され、主に以下のような理由が挙げられています。

nullable キーワードの曖昧さ

  • nullable: true は「型指定されたスキーマにおいて null を許容する」という意図で導入されたが、OpenAPI 3.0 のドキュメントではその意味や他キーワードとの相互作用が十分に明確化されていなかった。
  • nullable: false(デフォルト値)についても、null を明示的に禁止するのか、単に変更しないのかが明確でなかった。

JSON Schema との整合性

これらの曖昧さや、JSON Schema への整合性の無さにより、バリデーターの挙動に一貫性がありませんでした。そのため OpenAPI 3.1.0 ではnullableが廃止され、type にnullを指定する方法に変更されました。

コード生成への影響

自分のチームでは OpenAPI で定義したスキーマを元にコードを自動生成しているプロジェクトがあります。oapi-codegenopenapi-typescriptを用いて Go のモデル定義や TypeScript の型を自動生成しています。

OpenAPI のバージョンの違いによって変わる null 値の表現方法がツールによるコード生成にどう影響を与えるか確認してみようと思います。

TypeScript での生成コード

openapi-typescript を使用して TypeScript のコードを生成しようとした場合、以下のようになります。

// OpenAPI 3.0.0 での生成コード
export interface components {
  schemas: {
    User: {
      id: number;
      name: string;
      email: string | null;
    };
  };
}

// OpenAPI 3.1.0 での生成コード
export interface components {
  schemas: {
    User: {
      id: number;
      name: string;
      email: string | null;
    };
  };
}

TypeScript の場合、生成されるコードに大きな違いはなく、nullalbe として指定したプロパティは自動生成されたコードに反映されているのがわかるかと思います。

Go での生成コード

oapi-codegen を使用して Go のコードを生成しようとした場合、以下のようになります。

// OpenAPI 3.0.0 での生成コード
type User struct {
    Email *string `json:"email"`
    Id    int     `json:"id"`
    Name  string  `json:"name"`
}

// OpenAPI 3.1.0 での生成コード
// WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/oapi-codegen/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x

Go の場合、OpenAPI 3.0.0 で生成されたコードは nullable として指定したプロパティが反映されているのがわかります。一方 OpenAPI 3.1.0 ではコードの自動生成がされず 3.0.0 にバージョンを落として実行するように警告が出されます。これは oapi-codegen が依存している kin-openapi で OpenAPI 3.1 系にはまだ対応していないためこのような挙動になっているとのことでした。

ここでは取り扱いませんがこちらで言及されている OpenAPI Overlay を使って OpenAPI 3.1.0 を 3.0.0 に downgrade する手法もあるみたいです。

まとめ

OpenAPI における null 値の表現方法について、仕様の違いとツールの対応状況を確認してきました。

  1. 仕様の違い

    • OpenAPI 3.0.0 では nullable: true による独自拡張
    • OpenAPI 3.1.0 では type: [string, "null"] による JSON Schema 準拠
    • 3.1.0 での変更は、より標準的な方法での null の表現を可能に
  2. 実際のツール対応状況

    • TypeScript (openapi-typescript)
      • 3.0.0, 3.1.0 ともに問題なく動作
    • Go (oapi-codegen)
      • 3.0.0 では正常に動作
      • 3.1.0 は現時点で未対応(kin-openapi の制限)
      • 3.1.0 を使用する場合は OpenAPI Overlay による downgrade が必要

新規プロジェクトで新しく API スキーマを書く場合、JSON Schema との互換性がある 3.1.0 の方式が望ましいものの、実際の採用にあたってはツールの対応状況を十分に確認する必要がありそうです。もし既存プロジェクトで 3.1.0 に移行する場合でも使っているツールの対応状況を把握してから移行するのが良さそうです。