深入理解 PATCH 请求中的三种 Content-Type
3 min read
目录

作为现代 Web 开发中常用的 HTTP 方法之一,PATCH 用于对资源进行部分更新。然而在实际开发中,很多开发者对 PATCH 请求中不同的 Content-Type 使用场景存在困惑。本文将详细解析三种常见的 Content-Type 类型及其适用场景。

1. application/json-patch+json (RFC 6902)

设计理念

application/json-patch+json 是遵循 RFC 6902 标准的 Content-Type,它采用操作指令的方式描述对资源的修改。

核心特点

  • 原子性操作:所有操作要么全部成功,要么全部失败
  • 精确控制:支持多种操作类型(add/remove/replace/move/copy/test)
  • 操作数组:请求体必须是 JSON 格式的操作数组

代码示例

// 更新用户信息示例
const updateUser = async (userId: string) => {
  const response = await fetch(`/api/users/${userId}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json-patch+json',
      'Authorization': 'Bearer token'
    },
    body: JSON.stringify([
      { "op": "replace", "path": "/username", "value": "new_username" },
      { "op": "add", "path": "/age", "value": 28 },
      { "op": "remove", "path": "/tempField" }
    ])
  });
  return response.json();
};

适用场景

  • 需要确保多个字段更新具有原子性
  • 需要条件性更新(配合 test 操作)
  • 需要执行复杂操作(如数组元素移动)

2. application/merge-patch+json (RFC 7396)

设计理念

application/merge-patch+json 遵循 RFC 7396 标准,采用简单的合并策略更新资源。

核心特点

  • 简单直观:只需提供要更新的字段
  • 字段删除:通过将字段设为 null 实现删除
  • 非原子性:各字段更新独立成功或失败

代码示例

// 合并更新用户信息
const updateUserProfile = async (userId: string) => {
  const response = await fetch(`/api/users/${userId}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/merge-patch+json',
      'Authorization': 'Bearer token'
    },
    body: JSON.stringify({
      username: "updated_name",
      profile: {
        bio: "New bio description",
        website: null // 删除website字段
      }
    })
  });
  return response.json();
};

适用场景

  • 简单的部分资源更新
  • 不需要复杂操作的条件更新
  • 前端只需要发送变更的字段

3. application/json (非标准用法)

现状分析

虽然很多 API 使用 application/json 作为 PATCH 请求的 Content-Type,但这并不是标准做法,不同 API 的实现可能不一致。

潜在问题

  • 语义模糊:可能被实现为完整替换(类似 PUT)
  • 缺乏标准:不同系统行为不一致
  • 字段删除:通常没有标准删除方式

代码示例(不推荐)

// 不推荐的用法示例
const updateUser = async (userId: string) => {
  const response = await fetch(`/api/users/${userId}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      // 通常需要发送完整对象
      id: userId,
      username: "new_username",
      age: 30
      // 缺失的字段可能被置空或保留原值
    })
  });
  return response.json();
};

三种类型对比表格

特性json-patch+jsonmerge-patch+jsonjson (非标准)
标准RFC 6902RFC 7396
请求体格式操作数组部分对象完整对象
原子性取决于实现
删除字段remove 操作设为 null通常不支持
测试操作支持不支持不支持
适用场景需要精确控制的复杂修改简单的部分更新不推荐用于 PATCH

常见问题

Q: 为什么不应该使用 application/json 作为 PATCH 的 Content-Type?

A: 主要原因有三点:

  1. 缺乏标准规范,不同 API 实现行为不一致
  2. 无法明确表达 ” 删除字段 ” 的意图
  3. 容易与 PUT 方法产生语义混淆

Q: 什么时候应该选择 json-patch 而不是 merge-patch?

A: 考虑以下场景时选择 json-patch:

  • 需要确保多个更新操作的原子性
  • 需要条件性更新(先检查某个值是否符合预期)
  • 需要对数组进行复杂操作(如移动元素位置)

总结

正确使用 PATCH 请求的 Content-Type 对于构建规范的 RESTful API 至关重要:

  1. 优先使用标准类型:根据需求选择 json-patch+jsonmerge-patch+json
  2. 避免使用非标准用法:不要使用 application/json 作为 PATCH 的 Content-Type
  3. 保持一致性:在整个项目中统一使用同一种 PATCH 风格

通过遵循这些规范,可以确保你的 API 行为明确、可预测,并且与其他系统良好兼容。