Web APIのプロパティ名、camelCaseかsnake_caseかで悩んだ

  • API Design
  • TypeScript
  • Swift
  • Kotlin

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

Web APIのJSONプロパティ名をcamelCaseにするかsnake_caseにするか、社内で話が出たので整理した。単なる好みの問題だと思ってたけど、実際は「変換コストをどこに持たせるか」の設計判断だった。

生成側の観点

TypeScript / React / GraphQL / AdonisJSあたりが中心なら、camelCaseがそのままJSONにシリアライズできて楽。

type User = {
userId: string;
createdAt: string;
};

DBがsnake_caseならマッピング層は要るけど、DTOと JSON の形が一致するのでアプリコード側の見通しは良くなる。

逆にOpenAPI First・REST API・多言語クライアント前提ならsnake_caseの方が有利。SQLとの親和性が高いし、Go/Python/Rubyの慣習にも合う。

{
"user_id": "...",
"created_at": "..."
}

Swift/Kotlinクライアント側の話

ここが一番効いてくるところだった。SwiftのAPI Design Guidelines(swift.org)はlowerCamelCaseを明記してる。なので、snake_caseなAPIを受ける場合はCodingKeysで変換が要る。

struct User: Decodable {
let userId: String
let createdAt: Date
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case createdAt = "created_at"
}
}

Kotlinも同様で、Gsonなら@SerializedNameで対応できる。

data class User(
@SerializedName("user_id")
val userId: String,
@SerializedName("created_at")
val createdAt: Instant
)

ただしMoshiを使ってる場合はこの変換がそのまま効かないケースがあるらしく、KotlinJsonAdapterFactoryを別途噛ませる必要が出てくることがあるみたい(実機未確認)。ライブラリ次第で挙動が変わるので、そこは要注意。

SDK自動生成なら話は変わる

OpenAPI GeneratorやSwagger CodegenはmodelPropertyNamingオプションでcamelCase/snake_case/PascalCase/originalを切り替えられる。なのでSDKを完全自動生成する前提なら、API側がsnake_caseでもクライアント側の不利は小さくなる。

とはいえGitHubのIssueを見ると、Kotlin生成でquery paramが意図せずcamelCaseに変換されて実際のリクエストが壊れる、みたいな報告もちらほらある。「自動生成だから安心」と全面的に信頼するのはちょっと危ない。

結論

  • TypeScript / React / Swift / Kotlinが主要クライアント → camelCaseが変換コスト最小で自然
  • 外部公開API / OpenAPI First / 多言語クライアント対応重視 → snake_caseに合理性あり
  • SDK完全自動生成前提なら、この差はある程度吸収される

うちの場合はTypeScript中心でSwift/Kotlinクライアントも持ってるので、DBはsnake_case、API(JSON)とアプリコードはcamelCaseで境界を一箇所に絞る方針に倒すのが良さそう。