# marshmallow vs Pydantic 徹底比較：設計思想・性能・エコシステムで選ぶ（2026年・意思決定ガイド）

> marshmallowとPydantic v2を公式仕様に基づき徹底比較。記述子型スキーマvs型アノテーション、Rust製コアの性能差、Flask/SQLAlchemyとFastAPIのエコシステム、双方向シリアライズ、同一データの複数ビュー、共存と移行までを実コードで解説し、案件に応じた選定基準を示します。

- 公開日: 2026-06-26
- 著者: 友田 陽大
- タグ: Python, marshmallow, Pydantic, シリアライズ, バリデーション, 型安全, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/marshmallow-vs-pydantic-comparison-guide

## 要点

- 設計の起点が違う：marshmallowは「Schema＋fields記述子」、Pydanticは「型アノテーション＋BaseModel」。プレゼンテーション指向か型ファーストか
- 性能はRust製pydantic-coreを持つPydanticが優位。一方marshmallowはonly/exclude/Pluckで同一データの複数ビューを柔軟に作り分けられる
- エコシステムで選ぶ：既存のFlask/SQLAlchemy資産ならmarshmallow、FastAPI新規・JSON Schema要件ならPydantic v2
- 両者は排他ではなく役割で選ぶ。「境界の外を信頼しない」という規律はどちらでも同一
- 移行は一方向ではない。スキーマ定義・バリデータ・シリアライズの対応関係を表で示し、安全に乗り換える

---

## **導入：問いは「どちらが優れているか」ではない**

「marshmallow と Pydantic、どちらを使うべきか」——Python のバックエンド設計で繰り返される問いです。しかし、この問いの立て方そのものが、しばしば判断を誤らせます。**正しい問いは「いつ、どちらを選ぶか」**です。両者は競合する代替品ではなく、設計思想の起点が異なる二つの道具であり、案件の制約（既存スタック・性能要件・チームの型運用文化）によって最適解が入れ替わります。

両者に共通する本質は一つです——**「システム境界の外から来るデータを、決して信頼しない」**。HTTP リクエストボディ、外部 API のレスポンス、フォーム入力、メッセージキューのペイロード。これらは「型の保証がない、検証されていないデータ」であり、内側に素通しさせた瞬間、`KeyError`／`AttributeError`、最悪の場合は**マスアサインメント（不正な権限昇格）やデータ漏洩**に化けます。marshmallow も Pydantic も、この境界に立つ**門番**である点では完全に一致します。違うのは、門番をどう「宣言」するかです。

筆者は、経済産業大臣賞を受賞した B2B SaaS のバックエンドを **Python / Flask / SQLAlchemy / PostgreSQL** で設計・実装し、その境界バリデーションを **marshmallow** で担ってきました。一方、FastAPI ベースの新規プロジェクトでは **Pydantic v2** を採用しています。両方を本番で運用してきた立場から、本記事は「どちらが勝者か」ではなく、**目の前の案件でどちらを選ぶべきかを機械的に判断できる基準**を、公式仕様に忠実な実コードで提示します。

> 💡 **この記事で扱うバージョン**：marshmallow **4.3.0**（2026年4月時点の安定版）と **Pydantic v2** を前提とします。Web 上の記事や生成 AI が出力するコードには、marshmallow 3.x の旧スタイル（`missing=`／`default=`）や Pydantic v1 のスタイル（`@validator`／`.dict()`）が混在しがちです。本記事はいずれも最新の正準スタイルで統一しています。

> 💡 各ライブラリ単体の深掘りは、それぞれの実践ガイドが対になります。marshmallow の双方向シリアライズと境界設計は [marshmallow 実践ガイド](/blog/marshmallow-python-serialization-validation-production-guide)、Pydantic v2 の型ファースト検証は [Pydantic v2 実践ガイド](/blog/pydantic-v2-production-validation-type-safety) を併読すると、本記事の比較がより立体的に理解できます。

---

## **1. 設計思想の違い：記述子で「外から宣言」か、型から「導出」か**

両者の最大の違いは、文法でも性能でもなく、**スキーマをどこから導くか**という思想にあります。ここを理解すれば、残りの差はすべてその帰結として説明できます。

### **marshmallow：`Schema` ＋ `fields` 記述子（外から宣言する）**

marshmallow は、`Schema` クラスのクラス属性に `fields` の**記述子オブジェクト**を並べます。フィールドは「Python の型」とは独立した、**シリアライズ／デシリアライズの仕様**として外から宣言されます。

```python
from marshmallow import Schema, fields, validate


class UserSchema(Schema):
    name = fields.Str(required=True, validate=validate.Length(min=1, max=120))
    email = fields.Email(required=True)
    age = fields.Int(validate=validate.Range(min=18))
```

ここで `fields.Str()` は型注釈ではなく**フィールド記述子のインスタンス**です。「この項目は文字列として入出力し、長さ 1〜120 で検証する」という**振る舞いの宣言**であり、Python の `str` 型そのものとは切り離されています。これが marshmallow を「型システムに縛られない、プレゼンテーション指向のシリアライザ」たらしめている核心です。

### **Pydantic v2：型アノテーション ＋ `BaseModel`（型から導出する）**

Pydantic は、`BaseModel` を継承したクラスに**標準の型アノテーション**を書きます。検証ルールは型そのものと `Field()` から**導出**されます。

```python
from pydantic import BaseModel, Field, EmailStr


class User(BaseModel):
    name: str = Field(min_length=1, max_length=120)
    email: EmailStr
    age: int = Field(ge=18)
```

`name: str` は本物の型アノテーションです。IDE はこれを `str` として補完し、`mypy`／`pyright` は `user.name` を `str` と推論します。スキーマは「型の定義」と一体であり、別途記述子を並べる必要がありません。

**なぜこの違いが決定的なのか？**
marshmallow の記述子方式は、**同じデータに対して複数の異なる「見せ方」を、型を変えずに宣言できる**柔軟性を生みます（後述の複数ビュー）。一方 Pydantic のアノテーション方式は、**スキーマとアプリケーションの型が一つになる**ため、型チェッカーと IDE の支援が最大化されます。これは ETC（Easy To Change）のトレードオフです——marshmallow は「出力表現の変更」に強く、Pydantic は「型の一貫性の維持」に強い。どちらが優れているかではなく、**変更しやすくしたい軸が違う**のです。

---

## **2. 同じ題材を両方で書く：User 登録スキーマを side-by-side で**

抽象論より実物です。**同一の「ユーザー登録」スキーマ**を両ライブラリで実装し、検証（load／validate）と整形（dump／serialize）の違いを並べて見ます。要件は共通です。

- `name`：必須、1〜120 文字
- `email`：必須、メール形式
- `password`：必須、12 文字以上、**入力専用**（レスポンスに出さない）
- `id` / `created_at`：**出力専用**（クライアントは書き込めない）

### **marshmallow 版**

```python
from marshmallow import Schema, fields, validate, post_load, ValidationError
from dataclasses import dataclass
from datetime import datetime


@dataclass
class User:
    name: str
    email: str
    password: str


class UserSchema(Schema):
    id = fields.Int(dump_only=True)               # 出力専用：load では無視される
    created_at = fields.DateTime(dump_only=True)
    name = fields.Str(required=True, validate=validate.Length(min=1, max=120))
    email = fields.Email(required=True)
    password = fields.Str(load_only=True, required=True, validate=validate.Length(min=12))

    @post_load
    def make_user(self, data, **kwargs):
        return User(**data)                       # 検証済み dict をドメインオブジェクトへ写像


schema = UserSchema()

# --- load（デシリアライズ＋検証）---
try:
    user = schema.load({"name": "友田", "email": "a@b.com", "password": "correct-horse"})
except ValidationError as err:
    print(err.messages)                           # 例: {'email': ['Not a valid email address.']}

# --- dump（シリアライズ）---
schema.dump({"id": 1, "created_at": datetime.now(),
             "name": "友田", "email": "a@b.com", "password": "secret"})
# → {'id': 1, 'created_at': '2026-...', 'name': '友田', 'email': 'a@b.com'}
#   password は load_only のため出力に含まれない
```

### **Pydantic v2 版**

```python
from pydantic import BaseModel, Field, EmailStr, ValidationError
from datetime import datetime


class UserIn(BaseModel):
    """入力（登録リクエスト）用モデル。"""
    name: str = Field(min_length=1, max_length=120)
    email: EmailStr
    password: str = Field(min_length=12)


class UserOut(BaseModel):
    """出力（レスポンス）用モデル。password を含めない別モデルで漏洩を構造的に防ぐ。"""
    id: int
    created_at: datetime
    name: str
    email: EmailStr


# --- validate（デシリアライズ＋検証）---
try:
    user = UserIn.model_validate({"name": "友田", "email": "a@b.com", "password": "correct-horse"})
except ValidationError as err:
    print(err.errors())                           # 構造化されたエラーのリスト

# --- serialize ---
UserOut(id=1, created_at=datetime.now(), name="友田", email="a@b.com").model_dump()
# → {'id': 1, 'created_at': datetime(...), 'name': '友田', 'email': 'a@b.com'}
```

ここに両者の性格がくっきり現れます。

| 関心事 | marshmallow | Pydantic v2 |
| --- | --- | --- |
| 検証＋デシリアライズ | `schema.load(data)` → dict（または `@post_load` で任意の型） | `Model.model_validate(data)` → そのモデルのインスタンス |
| JSON 文字列から | `schema.loads(json_str)` | `Model.model_validate_json(json_str)` |
| シリアライズ | `schema.dump(obj)` / `schema.dumps(obj)` | `model.model_dump()` / `model.model_dump_json()` |
| 入力専用フィールド | `load_only=True`（1 スキーマ内で表現） | 入力用モデルにのみ定義（モデルを分ける） |
| 出力専用フィールド | `dump_only=True`（1 スキーマ内で表現） | 出力用モデルにのみ定義（モデルを分ける） |

> 💡 **設計の縮図がここにある**：marshmallow は **1 つの `UserSchema`** に `load_only` / `dump_only` を宿し、入口と出口を一枚のスキーマで表現しました。Pydantic は **`UserIn` / `UserOut` という 2 つのモデル**に分割しました。これは優劣ではなく思想の差です。marshmallow は「一つのデータの複数の見せ方」を一枚で、Pydantic は「役割ごとに型を明確に分ける」ことを得意とします。

---

## **3. 機能・特性マトリクス**

公式仕様に基づく、主要な観点の比較です。「速度」「型チェッカー連携」など定性的な項目は、第 5 章以降で根拠を述べます。

| 観点 | marshmallow | Pydantic v2 |
| --- | --- | --- |
| スキーマ定義 | `Schema` クラス ＋ `fields` 記述子（外から宣言） | 型アノテーション ＋ `BaseModel`（型から導出） |
| 主眼 | 双方向シリアライズ／デシリアライズ（プレゼンテーション指向） | 型駆動のドメインモデル＆検証（型ファースト） |
| 検証の入口 | `schema.load(data)` / `schema.loads(json)` | `Model.model_validate()` / `model_validate_json()` |
| シリアライズ | `schema.dump()` / `schema.dumps()` | `model.model_dump()` / `model_dump_json()` |
| 実装言語・速度 | 純 Python 実装 | コアは Rust 製 `pydantic-core`（公式が v1 比で大幅高速化と説明） |
| 同一データの複数ビュー | `only` / `exclude` / `Pluck` で柔軟に作り分け | ビューごとにモデルを分けるのが基本 |
| JSON Schema 出力 | 標準では非対応（別ライブラリが必要） | `model_json_schema()` で自動生成 |
| 型チェッカー連携 | 記述子ベースでやや弱め | アノテーション直結で強力（補完・静的解析が効く） |
| 双方向性 | シリアライズ／デシリアライズが対称な第一級機能 | 検証（入力）が主、シリアライズは `model_dump` で対応 |
| カスタム検証 | `validate=` / `@validates` / `@validates_schema` | `@field_validator` / `@model_validator` |
| 代表的な相棒 | Flask、SQLAlchemy（`marshmallow-sqlalchemy`） | FastAPI（ネイティブ統合） |

この表の一行一行は、第 1 章の「記述子 vs 型アノテーション」という根の違いから派生していることを意識すると、丸暗記ではなく**理由とともに**選定できます。

---

## **4. バリデーションの書き味：カスタムロジックの対応関係**

型検証だけでは「割引後価格は定価以下」のような業務ルールは表現できません。両者ともカスタム検証の仕組みを持ち、概念は対応します。

### **marshmallow：`@validates` / `@validates_schema`**

```python
from marshmallow import Schema, fields, validates, validates_schema, ValidationError


class PriceSchema(Schema):
    list_price = fields.Decimal(required=True)
    sale_price = fields.Decimal(required=True)

    @validates("list_price", "sale_price")        # フィールド単位（v4 は複数名・data_key を受け取る）
    def validate_positive(self, value, data_key):
        if value <= 0:
            raise ValidationError(f"{data_key} は正の値である必要があります。")

    @validates_schema                              # フィールド間の不変条件
    def validate_discount(self, data, **kwargs):
        if data["sale_price"] > data["list_price"]:
            raise ValidationError("販売価格は定価以下にしてください。", "sale_price")
```

### **Pydantic v2：`@field_validator` / `@model_validator`**

```python
from decimal import Decimal
from pydantic import BaseModel, field_validator, model_validator


class Price(BaseModel):
    list_price: Decimal
    sale_price: Decimal

    @field_validator("list_price", "sale_price")   # フィールド単位
    @classmethod
    def validate_positive(cls, v: Decimal) -> Decimal:
        if v <= 0:
            raise ValueError("正の値である必要があります。")
        return v

    @model_validator(mode="after")                 # 全フィールド確定後の関係検証
    def validate_discount(self) -> "Price":
        if self.sale_price > self.list_price:
            raise ValueError("販売価格は定価以下にしてください。")
        return self
```

対応はほぼ綺麗に取れます。**フィールド単位**は `@validates` ↔ `@field_validator`、**フィールド間**は `@validates_schema` ↔ `@model_validator(mode="after")`。違いは細部にあります。marshmallow のバリデータは `ValidationError` を `raise` し、Pydantic は標準の `ValueError`（または `AssertionError`）を投げると Pydantic が `ValidationError` に包んでくれます。また Pydantic の `@field_validator` は `@classmethod` を伴い、検証後に**値を返す**（変換も兼ねる）点が marshmallow と異なります。

> ⚠️ **バージョン由来の落とし穴**：marshmallow v4 では、バリデータが `False` を返す旧スタイルは廃止され、**必ず `ValidationError` を `raise`** する必要があります。Pydantic でも v1 の `@validator` は非推奨で、v2 では `@field_validator` ＋ `@classmethod` が正準です。生成 AI が古いスタイルを出力したら、ここを真っ先に疑ってください。

---

## **5. 性能とアーキテクチャ：Rust 製コアという構造的な差**

性能を語るとき、まず**構造の違い**を押さえるべきです。**Pydantic v2 のコア `pydantic-core` は Rust で実装**されており、検証・シリアライズのホットパスがネイティブコードで動きます。Pydantic 公式は、この再設計により **v1 比で大幅に高速化した**と説明しています。一方 **marshmallow は純 Python 実装**であり、その分のオーバーヘッドは構造的に存在します。

ただし——**ここで誇張した数値を並べることはしません**。実アプリケーションのレイテンシは、DB クエリ・ネットワーク I/O・ビジネスロジックが支配的であり、検証ライブラリの差がボトルネックになるのは、**高 QPS でペイロードが大きく、かつ I/O が極小**という特定条件下です。多くの CRUD API では、どちらを選んでも体感差は出ません。

**なぜ「Rust 製だから常に Pydantic」とはならないのか？**
性能は選定の**一軸**に過ぎないからです。CLAUDE.md の原則「推測で最適化するな、計測してから最適化せよ」に従えば、**まず計測**し、検証がボトルネックだと**証明されてから**性能を判断材料にすべきです。多くの案件では、後述するエコシステム適合や複数ビューの柔軟性のほうが、生の検証速度より事業価値に直結します。性能が真に支配的要件（例：超高スループットなゲートウェイ、大量バッチ変換）であれば、Pydantic の Rust コアは明確なアドバンテージになります——そのときは堂々と性能で選んでください。

---

## **6. 双方向シリアライズと「複数ビュー」：marshmallow の主戦場**

marshmallow が最も輝くのは、**同一のデータソースから、文脈に応じた複数の表現を作り分ける**場面です。記述子が型から独立しているからこそ、一枚のスキーマを `only` / `exclude` / `Pluck` で削り出せます。

```python
from marshmallow import Schema, fields


class UserSchema(Schema):
    id = fields.Int()
    name = fields.Str()
    email = fields.Email()
    role = fields.Str()
    bio = fields.Str()
    created_at = fields.DateTime()


# 一覧 API：必要最小限だけ
list_view = UserSchema(only=("id", "name"))

# 詳細 API：機密以外を全部
detail_view = UserSchema(exclude=("role",))

# 管理用 API：すべて見せる
admin_view = UserSchema()

# ネスト先の 1 属性だけを平坦化して取り出す
class PostSchema(Schema):
    title = fields.Str()
    author = fields.Pluck(UserSchema, "name")     # → {"title": "...", "author": "友田"}
```

`only` / `exclude` は**ドット記法で多階層**も指定できるため、`UserSchema(only=("posts.title",))` のようにネストの深部だけを抜き出すことも可能です。**一つの真実の定義（Single Source of Truth）から、ビューを動的に削り出す**——これが marshmallow の設計上の主戦場です。

Pydantic でも `model_dump(include=..., exclude=...)` で出力を絞れますが、思想はやや異なります。Pydantic は**ビューごとに `UserList` / `UserDetail` / `UserAdmin` といった専用モデルを定義する**のが定石で、これは「各レスポンスの型が明確になる」「FastAPI の `response_model` と JSON Schema が正確になる」という利点と引き換えに、モデル数が増える方向に働きます。

**なぜこの差が設計判断に効くのか？**
DRY の観点では marshmallow の「一枚から削り出す」が魅力的に見えます。しかし SRP と型の明確性の観点では Pydantic の「ビューごとに型を分ける」にも強い理がある——各モデルが「このレスポンスはこの形」という単一の責務を持つからです。**ビューのバリエーションが多く流動的なら marshmallow、ビューの型を API 契約として固定したいなら Pydantic**。これが第 1 章の思想差の、最も実務的な現れ方です。

---

## **7. エコシステムで選ぶ：Flask/SQLAlchemy か、FastAPI/JSON Schema か**

実際の選定で最も重く効くのは、思想でも性能でもなく、**周辺エコシステムとの適合**です。ライブラリは単体では動きません。

### **marshmallow の生息域：Flask ＋ SQLAlchemy**

marshmallow は ORM・フレームワーク非依存ですが、`marshmallow-sqlalchemy` により **SQLAlchemy モデルからスキーマを自動生成**でき、Flask スタックで事実上の標準として成熟しています。

```python
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema


class AuthorSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Author              # 列定義からフィールドを自動生成
        load_instance = True        # load() が ORM インスタンスを返す → そのまま session.add()
```

`load_instance=True` により、`schema.load(request.get_json())` が検証済みの ORM インスタンスを返し、ビュー関数は「検証して保存する」だけになります。**既存の Flask/SQLAlchemy 資産があるなら、marshmallow がエコシステム上の自然な選択**です。SQLAlchemy 側の型運用は [SQLAlchemy 2.0 実践ガイド](/blog/sqlalchemy-2-typed-orm-production-guide)、マイグレーションは [Alembic ゼロダウンタイム移行](/blog/alembic-zero-downtime-migrations-sqlalchemy) が対になります。

### **Pydantic の生息域：FastAPI ＋ JSON Schema**

Pydantic は **FastAPI とネイティブに統合**されています。リクエストボディ・レスポンスモデル・依存性注入のすべてが Pydantic モデルで表現され、さらに **`model_json_schema()` が JSON Schema を自動生成**するため、OpenAPI ドキュメントと Swagger UI が**コードから自動で**生成されます。

```python
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class ItemIn(BaseModel):
    name: str
    price: float


@app.post("/items", response_model=ItemIn)
def create_item(item: ItemIn):    # 検証・OpenAPI スキーマ・補完がすべて自動
    return item
```

**FastAPI で新規構築する、あるいは JSON Schema / OpenAPI を要件とするなら、Pydantic 一択**です。これは性能の話ではなく、フレームワークとの結合度の話です。FastAPI の本番運用設計は [FastAPI 本番運用ガイド](/blog/fastapi-production-async-pydantic-observability-guide) で詳説しています。

> 💡 **エコシステムは性能より重い**：新規 API を FastAPI で書くなら、たとえ marshmallow に慣れていても Pydantic を選ぶのが合理的です。逆に、20 万行の Flask/SQLAlchemy 資産がある現場に Pydantic を持ち込めば、`response_model` の自動化も `load_instance` の利便も失い、接着剤を書く羽目になります。**「使い慣れた方」ではなく「スタックが求める方」を選ぶ**——これが最も外さない基準です。

---

## **8. 選定フローチャート：意思決定ツリー**

ここまでの軸を、上から順に評価するだけで結論が出る形に圧縮しました。**上の分岐ほど決定力が強い**ように並べています。

- **Q1. Web フレームワークは何か？**
  - **FastAPI（または新規でこれから選ぶ）** → **Pydantic v2**。ネイティブ統合・JSON Schema 自動生成の恩恵が大きく、ここで決まることが多い。
  - **Flask（既存資産あり）** → 次へ。
- **Q2. 既存スタックに SQLAlchemy ＋ marshmallow 資産があるか？**
  - **ある** → **marshmallow** を継続。`marshmallow-sqlalchemy` の `load_instance` 連携を捨てる理由がない限り、乗り換えコストに見合わない。
  - **ない／グリーンフィールド** → 次へ。
- **Q3. JSON Schema / OpenAPI の自動生成は要件か？**
  - **要件である** → **Pydantic v2**（`model_json_schema()` が標準）。
  - **不要** → 次へ。
- **Q4. 同一データの「複数ビュー」を頻繁かつ柔軟に作り分けるか？**
  - **作り分けが多く流動的** → **marshmallow**（`only` / `exclude` / `Pluck` の柔軟性が効く）。
  - **ビューの型を API 契約として固定したい** → **Pydantic v2**（ビューごとのモデルが契約になる）。
- **Q5. 検証・変換が計測上のボトルネックで、超高スループットが要件か？**
  - **そうである** → **Pydantic v2**（Rust 製 `pydantic-core`）。ただし**計測で証明してから**判断すること。
  - **そうでない** → ここまでの分岐で出た結論に従う。性能で覆さない。

迷ったら最上位の Q1・Q2 に戻ってください。**フレームワークと既存資産だけで、現実の案件の大半は決着します**。

---

## **9. 共存と移行：排他ではなく、役割で分ける**

最後に、実務で最も誤解されやすい点を正します——**両者は同一プロジェクトで共存できます**。「どちらか一方に統一しなければならない」という思い込みは不要です。

### **共存：レイヤーごとに役割分担する**

たとえば、FastAPI で外部 API を提供しつつ、内部のレポート生成では複数ビューの柔軟さが欲しい、という構成は現実的です。**境界（HTTP）は Pydantic、プレゼンテーション整形は marshmallow**、と層で分けても破綻しません。重要なのは「境界の外を信頼しない」という規律であり、それはどちらでも同一に守れます。両者を混ぜる際の唯一の注意は、**一つの境界に対する真実の定義は一つにする**こと——同じリクエストを両方で二重に検証して定義が二系統に割れる、という DRY 違反だけは避けてください。

### **移行：対応表で機械的に乗り換える**

一方から他方へ移行する場合、概念の対応はほぼ一対一で取れます。下表をたどれば、機械的に置き換えられます。

| 概念 | marshmallow | Pydantic v2 |
| --- | --- | --- |
| スキーマ／モデル定義 | `class S(Schema):` ＋ `fields.*` | `class M(BaseModel):` ＋ 型アノテーション |
| 検証＋デシリアライズ | `schema.load(data)` | `Model.model_validate(data)` |
| JSON から | `schema.loads(json_str)` | `Model.model_validate_json(json_str)` |
| シリアライズ（dict） | `schema.dump(obj)` | `model.model_dump()` |
| シリアライズ（JSON） | `schema.dumps(obj)` | `model.model_dump_json()` |
| フィールド単位の検証 | `@validates("x")` | `@field_validator("x")` ＋ `@classmethod` |
| フィールド間の検証 | `@validates_schema` | `@model_validator(mode="after")` |
| フィールド制約 | `validate=validate.Length(min=1)` ／ `validate.Range(min=0)` | `Field(min_length=1)` ／ `Field(gt=0)` |
| 必須 | `required=True` | アノテーションにデフォルト値を与えない |
| 入力時デフォルト | `load_default=...` | `Field(default=...)` |
| 別名（外部キー名） | `data_key="userName"` | `Field(alias="userName")` |
| 入力専用 | `load_only=True` | 入力用モデルにのみ定義 |
| 出力専用 | `dump_only=True` | 出力用モデルにのみ定義 |
| 厳格モード | `unknown=RAISE`（既定で未知キー拒否） | `ConfigDict(strict=True)` ／ `extra="forbid"` |

> ⚠️ **移行で最も多い取り違え**：marshmallow の `load_only` / `dump_only` は **1 スキーマ内のフラグ**ですが、Pydantic では **入力モデルと出力モデルを分割**するのが定石です。表の機械置換でフラグだけ消して 1 モデルに押し込むと、第 2 章で見た `password` 漏洩のような事故を再導入しかねません。**「フラグ → モデル分割」という構造の翻訳**を忘れないでください。

**なぜ「移行は一方向ではない」と言えるのか？**
対応表が双方向に読めるからです。FastAPI 化に伴って marshmallow → Pydantic へ進むこともあれば、複数ビュー要件の増大で Pydantic のモデル爆発に悩み、プレゼンテーション層だけ marshmallow を導入することもあります。**ライブラリは目的を達成する手段であり、思想に殉じる対象ではありません**。案件の制約が変われば、最適な手段も変わります。

---

## **結論：問いを「いつ・どちらを」に置き換える**

marshmallow と Pydantic は、優劣で語る対象ではありません。**設計の起点が異なる二つの門番**であり、案件の制約が選定を決めます。本記事の要点を再掲します。

1. **思想が違う**：marshmallow は「`Schema` ＋ `fields` 記述子を外から宣言」、Pydantic は「型アノテーションから導出」。プレゼンテーション指向か、型ファーストか。
2. **性能は Pydantic 優位**：Rust 製 `pydantic-core` を持つ。ただし**計測で証明されるまで**性能を選定軸にしない。多くの CRUD API では体感差は出ない。
3. **複数ビューは marshmallow 優位**：一枚の `Schema` から `only` / `exclude` / `Pluck` で削り出せる。Pydantic はビューごとにモデルを分ける思想。
4. **エコシステムが最も重い**：既存の Flask/SQLAlchemy 資産なら marshmallow、FastAPI 新規・JSON Schema 要件なら Pydantic v2。フレームワークと既存資産で大半は決着する。
5. **共存できる／移行は双方向**：同一プロジェクトで役割分担可能。対応表で機械的に乗り換えられるが、`load_only`/`dump_only` フラグは「モデル分割」へ翻訳する点に注意。
6. **規律は同一**：「境界の外を信頼しない」という本質は、どちらを選んでも変わらない。

「動くコード」と「10 年運用できるコード」の差は、ライブラリの銘柄ではなく、**境界の設計をどれだけ意識して道具を選んだか**に宿ります。両者の思想を理解していれば、目の前の案件に対して、根拠を持って最適な門番を選べます。

さらなる探求として、各ライブラリの単体ガイドと公式ドキュメントを、本記事の選定軸を念頭に再読することをお勧めします。

- [marshmallow 実践ガイド（双方向シリアライズと境界設計）](/blog/marshmallow-python-serialization-validation-production-guide)
- [Pydantic v2 実践ガイド（型ファースト検証）](/blog/pydantic-v2-production-validation-type-safety)
- [marshmallow 公式ドキュメント](https://marshmallow.readthedocs.io/en/stable/)
- [Pydantic 公式ドキュメント](https://docs.pydantic.dev/latest/)

---

### **型安全なバックエンド設計のご相談**

筆者は、ここで比較した「システム境界で外部入力を必ず検証し、内部の値を安全に整形して返す」という規律を、経済産業大臣賞を受賞した B2B SaaS の本番環境で、**Flask / SQLAlchemy / marshmallow** による境界バリデーションとして実装・運用してきました。FastAPI ベースのスタックでは、その役割を **Pydantic v2** が担います。ライブラリの選定は、性能や流行ではなく、**既存スタック・複数ビュー要件・JSON Schema 要件・チームの型運用文化**を踏まえた意思決定です。どちらが自社の案件に最適か、移行すべきか共存させるべきか——型安全な入力検証・レスポンス整形・マスアサインメント対策・ORM 連携といった、**事業の信頼性に直結する基盤**を、生成 AI を活用して高速かつ高品質に設計・実装します。Python を用いたバックエンド開発・既存システムの型安全化について、お気軽にご相談ください。
