OpenAPI で動的なキーを設定する


Gaji-Labo フロントエンドエンジニアの茶木です。

Gaji-Labo では、いくつかのスタートアップでプロダクト開発をしているのですが、 OpenAPI が活用されていることも少なくありません。

OpenAPI は、API の仕様を記述するためのフォーマットです。 今記事では、OpenAPI で定義するリクエストやレスポンスの型に、動的なキーを設定する方法について述べます。

動的なキーとは

fruitPrice:
  type: object
  properties:
    apple: number,
    banana: number,

基本、OpenAPI のオブジェクトのキー名には静的な値を指定します。
上記の例でも、オブジェクトの propertiesapplebanana とキー名を指定しています。

type FruitsPrice = Record<string, number>;
const fruitPrice: FruitsPrice = {
  apple: 280,
  banana: 89,
  cherry: 320,
  durian: 1090
  :
}

一方で、動的なキーは、TypeScript における Record<K, V> に相当します。型指定に具体的なキー名を指定せず、オブジェクトを操作するとき任意のキーを指定できます。

記述

OpenAPI では下記のように指定すると、キーは string 型であれば任意、値の型は numberになります。 前述の Record<string, number> 相当の指定です。

fruitPrice:
  type: object
  additionalProperties:
    type: number

動的なキー設定の使いどころ

動的なキー設定が役に立つケースは、事前にキー名を特定できないオブジェクトです。

translation_text:
  type: object
  additionalProperties:
    type: string

一例を挙げると語彙集です。

const texts = getTranslationApi("ja");  // Record<string, string>
// texts = { view: "表示", edit: "編集" };
function translateText(label: string) {
  return texts[label] ?? label
}

translateText("view");  // 表示
translateText("edit");  // 編集
translateText("print"); // print

getTranslationApitranslate_text を出力するものとします。translate_text は動的なキーを持つオブジェクトとなります。

この例では、view は “表示” と日本語が登録されていますが、print は “印刷” のような日本語で登録されていないので “print” と、label がそのまま(英語で)示されます。

こちら、view、edit、print だけが翻訳対象と割り切れれば、動的なキー設定をする必要はないのですが、頻繁な単語の追加や言語の追加がある運用であれば、OpenAPI の記載を都度書き換えなくて良いこの方法の利便性は高いでしょう。

任意のオブジェクトを指定できる動的なキーの例

特殊ですが、外部要因で規定されているフォーマットで、それを OpenAPI として書き起こすのが困難な場合です。かなり具体的な例を挙げると JSON Schema をAPIで取得して、バリデーションを含めた任意のフィールドを生成するケースがありました。

schema:
  type: object
  additionalProperties: true
  description: |-
    json-schema に従った JSON Object を記述する

この JSON Schema が外部要因で規定されているフォーマットに当たります。OpenAPI の型として書き起こせるのがベストですが、困難であるため、任意のキーで、値は任意の型のオブジェクトを受け取るようにしました。

additionalProperties: true
description: |-
    TypeScriptの Record<string, {}> 形式相当で出力される

コードの解説です。値を任意のオブジェクト型とするには、 additionalProperties: true とします。

additionalProperties: {}
description: |-
    TypeScriptの Record<string, any> 形式相当で出力される

合わせて、つまずきポイントを紹介します。 additionalProperties: {} とすると、値は any 型と認識されます。オブジェクト以外の値も許容されます。

additionalProperties: true とすると {} 型、
additionalProperties: {} とすると any 型となり 一見、逆なので要注意です。

なお、この例は JSON Schema としては特殊です。
というのは、JSON Schema は名前の通り JSON 形式であるため、string として受け取る方法でも実現できるためです。

JSON Schema は別記事がありますので合わせてお読みいただけるとうれしいです。

動的キーは使わずに済むならそれに越したことはない

OpenAPI で動的にキーを設定する方法と、使いどころを解説しました。

……が、実は、積極的な使用はおすすめしません。
というのは、静的なキーであれば型の不一致などで検出されるエラーが、動的なキーでは発見されないこともありますし、静的なキーを使用したコードよりリーダブルになることもないです。

動的キーの使用を回避する方法

先の語彙集の例でも、動的キーを使わない方法があります。

translation_text:
  type: array
  items: 
    type: object
    properties:
      key: string
      text: string
    required: object
      - key
      - text

こちらは常套手段で、 keyvalue をペアの配列を持つことで、任意キーのオブジェクトを避けられます。

const texts = getTranslationApi("ja");  // { key: string, text: string}[]
// texts = [{ key: "view", text: "表示"}, { key: "edit", text: "編集"}];
function translateText(key: string) {
  return texts.find((text => text.key === key)?.text ?? key;
}

translateText("view");  // 表示
translateText("edit");  // 編集
translateText("print"); // print

OpenAPI とフロントエンドのコードだけを見れば、このように静的なキーだけで記述できる方法が往々にしてあります。しかし、開発は連携によって行われるため、バックエンド側のAPIを生成する方法や、ユーザーの利便性に合わせて、動的キーを使うこともあります。

おわりに

OpenAPI での動的キーの使用は最小限に留めるべきと結論付けます。
それには、どのようにして使用を回避できるかを知っている必要があり、それでも使用するならば、バックエンドやユーザーサイドの理由も加味した決断を下すのが良いと考えます。

Gaji-Labo はさまざまな支援先チームがあるので、プロダクトによって使われている技術もさまざまです。状況ごとにあわせた動きをしてチームやプロダクトの成長を支えていけるように頑張っていきたいと思います。

Gaji-Labo フロントエンドエンジニア向けご案内資料

Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります

フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。

「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」

フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。

オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。

Next.js, React, TypeScript の相談をする!


投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。