DevToolBox免费
博客

Protocol Buffers vs JSON:gRPC vs REST

11 分钟阅读作者 DevToolBox

Protocol Buffers(Protobuf)和 JSON 是两种根本不同的数据序列化方式。JSON 以其人类可读的文本格式主导着 Web API,而 Protobuf 则以紧凑的二进制编码驱动着高性能微服务。本指南提供 Protobuf 与 JSON、gRPC 与 REST 的全面对比分析,包含真实基准测试、代码示例和实用的迁移策略。

使用我们的免费 JSON 转 Protobuf 工具进行格式转换 →

快速解答:应该使用哪个?

使用 JSON:构建公共 Web API、面向浏览器的应用、配置文件,或任何需要人类可读性的场景。JSON 被广泛支持,易于调试,且不需要模式定义。

使用 Protobuf:构建内部微服务、带宽受限的移动应用、IoT 设备,或任何性能至关重要的高吞吐系统。Protobuf 的载荷比 JSON 小 3–10 倍,序列化/反序列化速度快 2–5 倍。

什么是 Protocol Buffers?

Protocol Buffers(Protobuf)是 Google 开发的一种语言无关、平台无关、可扩展的结构化数据序列化机制。与 JSON 或 XML 使用文本不同,Protobuf 将数据编码为紧凑的二进制格式。

Protocol Buffers 的关键概念:

  • .proto 文件 — 使用特殊语法描述数据结构的模式定义
  • 代码生成protoc 编译器为目标语言(Go、Java、Python、C++ 等)生成类型安全的类
  • 二进制编码 — 数据使用变长整数和字段标签编码,产生比文本格式小得多的载荷
  • 字段编号 — 每个字段都有唯一的编号用于二进制编码,实现向前/向后兼容性
  • 强类型 — 每个字段都有定义的类型(int32、string、bool、enum、嵌套消息),在编译时强制执行
// user.proto — A simple Protobuf schema
syntax = "proto3";

package user.v1;

option go_package = "github.com/example/user/v1;userv1";

// User represents a registered user in the system.
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  UserRole role = 4;
  repeated string tags = 5;
  Address address = 6;
  google.protobuf.Timestamp created_at = 7;
}

enum UserRole {
  USER_ROLE_UNSPECIFIED = 0;
  USER_ROLE_ADMIN = 1;
  USER_ROLE_EDITOR = 2;
  USER_ROLE_VIEWER = 3;
}

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
  string zip_code = 4;
}

语法对比:JSON 与 Protobuf 中的相同数据

以下是用 JSON 和 Protobuf 定义表达的相同数据结构:

JSON 数据

{
  "id": 12345,
  "name": "Alice Chen",
  "email": "alice@example.com",
  "role": "admin",
  "tags": ["engineering", "backend", "lead"],
  "address": {
    "street": "123 Main St",
    "city": "San Francisco",
    "country": "US",
    "zipCode": "94102"
  },
  "createdAt": "2024-01-15T09:30:00Z"
}

Protobuf 模式(.proto)

syntax = "proto3";

package user.v1;

import "google/protobuf/timestamp.proto";

message User {
  int32 id = 1;           // field number 1
  string name = 2;        // field number 2
  string email = 3;       // field number 3
  UserRole role = 4;      // enum type
  repeated string tags = 5;  // repeated = array
  Address address = 6;    // nested message
  google.protobuf.Timestamp created_at = 7;
}

enum UserRole {
  USER_ROLE_UNSPECIFIED = 0;
  USER_ROLE_ADMIN = 1;
  USER_ROLE_EDITOR = 2;
  USER_ROLE_VIEWER = 3;
}

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
  string zip_code = 4;
}

使用生成的代码(Go 示例)

package main

import (
    "fmt"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/types/known/timestamppb"
    userv1 "github.com/example/user/v1"
)

func main() {
    // Create a user using generated code (type-safe!)
    user := &userv1.User{
        Id:    12345,
        Name:  "Alice Chen",
        Email: "alice@example.com",
        Role:  userv1.UserRole_USER_ROLE_ADMIN,
        Tags:  []string{"engineering", "backend", "lead"},
        Address: &userv1.Address{
            Street:  "123 Main St",
            City:    "San Francisco",
            Country: "US",
            ZipCode: "94102",
        },
        CreatedAt: timestamppb.Now(),
    }

    // Serialize to binary (34 bytes vs 280 bytes JSON)
    data, _ := proto.Marshal(user)
    fmt.Printf("Binary size: %d bytes\n", len(data))

    // Deserialize
    parsed := &userv1.User{}
    proto.Unmarshal(data, parsed)
    fmt.Println(parsed.Name) // "Alice Chen"
}

大小对比:二进制 vs 文本

Protobuf 最大的优势之一是载荷大小。二进制编码消除了字段名重复并使用变长整数。以下是相同数据的实际测量值:

数据类型JSON 大小Protobuf 大小缩减比例
简单对象(5 个字段)127 字节34 字节73%
100 个对象的数组12.7 KB3.4 KB73%
嵌套消息(3 层)2.1 KB0.4 KB81%
数字密集型数据8.5 KB1.2 KB86%
字符串密集型数据15.3 KB12.1 KB21%

注意:大小缩减因数据类型而异。Protobuf 对数字和布尔密集型数据节省最多,因为它使用变长编码(varints)。字符串密集型数据缩减较少,因为字符串本身在两种格式中的存储方式类似——只有字段名和结构开销被消除。

性能基准测试

处理相同数据集的典型性能特征(基于 Go、Java 和 C++ 实现的近似测量值):

指标JSONProtobuf
序列化(10K 对象)~45ms~12ms
反序列化(10K 对象)~55ms~10ms
内存分配(每次操作)~3.2 KB~0.8 KB
载荷大小(10K 对象)~1.2 MB~0.3 MB
GC 压力高(大量字符串分配)低(预分配结构体)

注意:JSON 性能因库而异。Go 的 encoding/json 相对较慢;json-iteratorsonic 等库可以显著缩小差距。在 JavaScript 中,V8 高度优化的 JSON.parse 对于小载荷可能优于 Protobuf.js。请始终使用你的具体数据和语言进行基准测试。

gRPC vs REST:协议对比

Protobuf 是 gRPC 的默认序列化格式,而 JSON 是 REST API 的标准。以下是两种方式的对比:

特性REST + JSONgRPC + Protobuf
传输协议HTTP/1.1 或 HTTP/2仅 HTTP/2
数据格式JSON(文本)Protobuf(二进制)
API 契约OpenAPI/Swagger(可选).proto 文件(必需)
代码生成可选(OpenAPI codegen)内置(protoc)
流式传输SSE、WebSocket(独立实现)原生双向流式传输
浏览器支持原生支持(fetch/XMLHttpRequest)需要 grpc-web 代理
负载均衡简单(HTTP 层)需要 L7 负载均衡器(Envoy 等)
超时/截止时间手动实现内置截止时间传播
认证Headers、OAuth、JWTMetadata、TLS、拦截器
缓存HTTP 缓存(ETags、CDN)无内置缓存机制

以下是同一个 API 分别用 REST 和 gRPC 定义的示例:

REST API (OpenAPI)

# REST endpoint
GET  /api/v1/users/{id}
POST /api/v1/users
PUT  /api/v1/users/{id}

# Request (POST /api/v1/users)
curl -X POST https://api.example.com/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alice Chen",
    "email": "alice@example.com",
    "role": "admin"
  }'

# Response
{
  "id": 12345,
  "name": "Alice Chen",
  "email": "alice@example.com",
  "role": "admin",
  "createdAt": "2024-01-15T09:30:00Z"
}

gRPC Service (.proto)

syntax = "proto3";

package user.v1;

service UserService {
  // Unary RPC — single request, single response
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);

  // Server streaming — single request, stream of responses
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // Bidirectional streaming
  rpc SyncUsers(stream SyncRequest) returns (stream SyncResponse);
}

message GetUserRequest {
  int32 id = 1;
}

message GetUserResponse {
  User user = 1;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  UserRole role = 3;
}

message CreateUserResponse {
  User user = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
}

// Client call with grpcurl:
// grpcurl -plaintext -d '{"id": 12345}' \
//   localhost:50051 user.v1.UserService/GetUser

模式演进:向前和向后兼容性

Protobuf 最强大的特性之一是对模式演进的支持。你可以在不破坏现有消费者的情况下演进数据结构。

安全模式演进的规则:

  • 永远不要重用字段编号 — 一旦使用了字段编号,即使删除该字段,也应视为永久占用
  • 使用 reserved — 将已删除的字段编号和名称标记为保留,防止意外重用
  • 自由添加新字段 — 使用新编号的新字段向后兼容;旧客户端会忽略它们
  • 不要更改字段类型 — 更改字段类型(如 int32 改为 string)会损坏数据
  • 对变体使用 oneof — 当字段可以是多种类型之一时,使用 oneof 而非可选字段
  • 使用 optional 关键字 — 在 proto3 中所有字段默认可选,但显式标记使意图更清晰

模式演进示例:

// Version 1 — Original schema
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

// Version 2 — Added new fields (backward compatible!)
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  string phone = 4;        // NEW: old clients ignore this
  UserRole role = 5;       // NEW: old clients ignore this
}

// Version 3 — Removed a field (use reserved!)
message User {
  int32 id = 1;
  string name = 2;
  // field 3 removed — email moved to ContactInfo
  reserved 3;
  reserved "email";
  string phone = 4;
  UserRole role = 5;
  ContactInfo contact = 6; // NEW: structured contact info

  // Use oneof for variant fields
  oneof notification_preference {
    string email_address = 7;
    string sms_number = 8;
    PushConfig push_config = 9;
  }
}

// Compare with JSON — no built-in evolution support:
// {
//   "id": 123,
//   "name": "Alice",
//   "email": "alice@example.com"   // Removed? Renamed?
//                                   // No schema to enforce!
// }

类型安全:强类型 vs 动态类型

Protobuf 的强类型系统相比 JSON 的动态特性是一个显著优势,尤其在大型代码库中:

特性JSONProtobuf
字段类型任何键可以是任何值(动态)严格定义(int32、string、bool 等)
枚举仅按约定使用字符串一等公民枚举类型,带验证
可空性null 对任何字段都是有效值可选包装类型,无 null
嵌套类型任意嵌套,无验证定义的消息类型,已验证
代码生成手动类型定义(TypeScript 接口)自动生成类型安全的类
编译时检查无(仅运行时错误)完整的编译时类型检查

示例:类型安全在实践中的差异:

// ---- JSON approach (TypeScript) ----
// You must manually define and maintain types
interface User {
  id: number;
  name: string;
  email: string;
  role: "admin" | "editor" | "viewer";
}

// Runtime: no guarantee the data matches the interface!
const response = await fetch("/api/users/123");
const user: User = await response.json();
// user.role could be "superadmin" — TypeScript won't catch it at runtime

// ---- Protobuf approach (generated code) ----
// Types are auto-generated from .proto — always in sync

// Go: compile error if you use wrong type
user := &userv1.User{
    Id:   12345,
    Name: "Alice",
    Role: userv1.UserRole_USER_ROLE_ADMIN,
    // Role: "admin",  // COMPILE ERROR: cannot use string as UserRole
}

// Java: compile error if field doesn't exist
User user = User.newBuilder()
    .setId(12345)
    .setName("Alice")
    .setRole(UserRole.USER_ROLE_ADMIN)
    // .setAge(30)  // COMPILE ERROR: no such method
    .build();

// Python: runtime validation with type hints
user = User(
    id=12345,
    name="Alice",
    role=UserRole.USER_ROLE_ADMIN,
    # role="admin"  # TypeError at runtime
)

工具生态系统

两个生态系统都有丰富的工具,但它们服务于不同的工作流:

工具用途
protoc官方 Protocol Buffers 编译器。从 .proto 文件为 10+ 种语言生成代码。
Buf现代 Protobuf 工具链。代码检查、破坏性变更检测、依赖管理和模式注册表(BSR)。
grpcurl与 gRPC 服务器交互的命令行工具,类似于 REST API 的 curl。
grpcuigRPC 服务的 Web UI。类似 Postman 的交互式请求构建器。
Postman同时支持 REST 和 gRPC。导入 .proto 文件,发送请求,查看响应。
BloomRPC / Evans带 GUI 的桌面 gRPC 客户端。加载 .proto 文件并交互式调用。
protovalidate直接在 .proto 文件中定义字段级验证规则(如 min/max、正则、必填)。
Connect RPC现代 gRPC 兼容框架。无需代理即可在浏览器中使用,生成地道的 TypeScript 客户端。
# Install protoc (Protocol Buffers compiler)
# macOS
brew install protobuf

# Linux
apt install -y protobuf-compiler

# Install Go plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Generate Go code from .proto
protoc --go_out=. --go-grpc_out=. proto/user/v1/user.proto

# --- Buf (modern alternative to protoc) ---
# Install buf
brew install bufbuild/buf/buf

# Initialize buf module
buf mod init

# Lint .proto files
buf lint

# Detect breaking changes
buf breaking --against '.git#branch=main'

# Generate code (configured via buf.gen.yaml)
buf generate

# --- grpcurl (curl for gRPC) ---
# List services
grpcurl -plaintext localhost:50051 list

# Describe a service
grpcurl -plaintext localhost:50051 describe user.v1.UserService

# Call an RPC
grpcurl -plaintext -d '{"id": 123}' \
  localhost:50051 user.v1.UserService/GetUser

何时使用 JSON

  • 公共 Web API — JSON 被所有 HTTP 客户端、浏览器和移动框架广泛理解
  • 浏览器前端JSON.parse()fetch() 原生工作,零依赖
  • 配置文件 — package.json、tsconfig.json、ESLint 配置都是 JSON(人类可读可编辑)
  • 调试和日志 — JSON 载荷可以直接在日志、网络检查器和终端输出中阅读
  • 快速原型开发 — 无需模式定义或代码生成步骤;直接发送和接收数据
  • 第三方集成 — Webhook、OAuth、支付 API(Stripe、PayPal)都使用 JSON
  • 小团队/简单服务 — 维护 .proto 文件的开销可能不值得
// JSON excels in web scenarios:

// 1. Browser fetch — zero dependencies
const response = await fetch("https://api.example.com/users/123");
const user = await response.json();
console.log(user.name); // Instantly usable

// 2. Configuration — human readable & editable
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true
  }
}

// 3. Webhook payload — universal format
app.post("/webhook", (req, res) => {
  const event = req.body; // JSON parsed automatically
  console.log(event.type); // "payment.completed"
});

// 4. Logging — readable in terminal
console.log(JSON.stringify({
  level: "info",
  message: "User created",
  userId: 123,
  timestamp: new Date().toISOString()
}));

何时使用 Protobuf

  • 内部微服务 — 服务间通信从类型安全和性能中获益最多
  • 移动应用 — 更小的载荷减少蜂窝网络上的电池使用和数据消耗
  • IoT 设备 — 带宽和处理能力受限的设备受益于紧凑的二进制编码
  • 高吞吐系统 — 交易平台、遥测管道和实时分析,每毫秒都很重要
  • 多语言架构 — 生成的代码确保 Go、Java、Python、C++、Rust 等之间的一致性
  • 流式数据 — gRPC 的原生流式传输支持非常适合实时推送、聊天和实时更新
  • 大型组织 — 模式注册表(Buf BSR)支持 API 治理、版本控制和破坏性变更检测
// Protobuf excels in backend/systems scenarios:

// 1. Microservice communication (Go)
func (s *OrderService) CreateOrder(ctx context.Context,
    req *orderv1.CreateOrderRequest,
) (*orderv1.CreateOrderResponse, error) {
    // Type-safe request — no parsing errors possible
    userId := req.GetUserId()      // int32, guaranteed
    items := req.GetItems()        // []*OrderItem, validated

    // Call another microservice via gRPC
    user, err := s.userClient.GetUser(ctx,
        &userv1.GetUserRequest{Id: userId})
    // ...
}

// 2. Kafka message serialization
producer.Send(&kafka.Message{
    Topic: "orders",
    Value: proto.Marshal(&orderv1.OrderEvent{
        Type:    orderv1.EventType_ORDER_CREATED,
        OrderId: 12345,
        UserId:  67890,
    }), // ~28 bytes vs ~180 bytes JSON
})

// 3. Mobile app (Kotlin/Android)
val channel = ManagedChannelBuilder
    .forAddress("api.example.com", 443)
    .useTransportSecurity()
    .build()
val stub = UserServiceGrpc.newBlockingStub(channel)
val user = stub.getUser(
    GetUserRequest.newBuilder().setId(123).build()
) // 73% smaller payload = less battery drain

迁移路径:JSON 到 Protobuf

从 REST/JSON 迁移到 gRPC/Protobuf 不必一步到位。以下是实用的渐进采用策略:

步骤 1:为现有 JSON 定义 .proto 模式

首先编写与现有 JSON 结构匹配的 .proto 定义。这是一个非侵入性的第一步。

// Existing JSON API response:
// GET /api/v1/products/123
{
  "id": 123,
  "name": "Wireless Mouse",
  "price": 29.99,
  "category": "electronics",
  "inStock": true,
  "tags": ["wireless", "bluetooth", "ergonomic"]
}

// Step 1: Write matching .proto schema
syntax = "proto3";

package product.v1;

message Product {
  int32 id = 1;
  string name = 2;
  double price = 3;
  string category = 4;
  bool in_stock = 5;
  repeated string tags = 6;
}

// Generate code: protoc --go_out=. product.proto
// Now you have type-safe Product structs — even before switching to gRPC

步骤 2:使用 gRPC-Gateway 进行转码

gRPC-Gateway 生成反向代理服务器,将 REST/JSON 请求转换为 gRPC 调用。这让你可以从同一个后端同时提供 REST 和 gRPC 服务:

// grpc-gateway — serve REST + gRPC from one codebase

// product.proto with HTTP annotations
syntax = "proto3";

import "google/api/annotations.proto";

service ProductService {
  rpc GetProduct(GetProductRequest) returns (Product) {
    option (google.api.http) = {
      get: "/api/v1/products/{id}"  // REST endpoint
    };
  }
  rpc CreateProduct(CreateProductRequest) returns (Product) {
    option (google.api.http) = {
      post: "/api/v1/products"
      body: "*"
    };
  }
}

// Architecture:
//
// Browser/REST clients                 gRPC clients
//        |                                  |
//        v                                  |
//  [gRPC-Gateway]  ---- HTTP/JSON ----      |
//        |                                  |
//        +--------> [gRPC Server] <---------+
//                       |
//                   [Business Logic]

步骤 3:内部服务优先采用 gRPC

从服务间通信开始。保持面向公众的 API 为 REST/JSON,同时内部服务通过 gRPC 通信:

// Migration architecture — gradual adoption
//
//  [Web Frontend]          [Mobile App]
//       |                       |
//   REST/JSON              gRPC/Protobuf (migrated!)
//       |                       |
//       v                       v
//  [API Gateway / gRPC-Gateway]
//       |
//       +--- gRPC ---> [User Service]     (migrated)
//       +--- gRPC ---> [Order Service]    (migrated)
//       +--- REST ---> [Legacy Payment]   (not yet migrated)
//       +--- gRPC ---> [Inventory Service] (new, born gRPC)

// Connect RPC — alternative that supports both protocols natively
// No proxy needed! Same handler serves gRPC + REST + Connect protocol

import (
    "connectrpc.com/connect"
    userv1connect "github.com/example/gen/user/v1/userv1connect"
)

func main() {
    mux := http.NewServeMux()
    path, handler := userv1connect.NewUserServiceHandler(&userServer{})
    mux.Handle(path, handler)
    // Serves gRPC, gRPC-Web, AND Connect protocol on same port
    http.ListenAndServe(":8080", h2c.NewHandler(mux, &http2.Server{}))
}

步骤 4:逐步迁移客户端

逐个迁移客户端。移动应用因更小的载荷和电池节省而从切换中获益最多。

// Client migration priority:
//
// 1. Mobile apps      — highest ROI (bandwidth savings)
// 2. Internal tools   — easy to control, low risk
// 3. Partner APIs     — provide .proto + generated SDKs
// 4. Public web API   — keep REST/JSON, add gRPC as option
//
// TypeScript client with Connect RPC (works in browsers!)
import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { UserService } from "./gen/user/v1/user_connect";

const transport = createConnectTransport({
  baseUrl: "https://api.example.com",
});

const client = createClient(UserService, transport);

// Type-safe API call — auto-generated from .proto!
const user = await client.getUser({ id: 123 });
console.log(user.name); // TypeScript knows this is string

重要提示:不要试图一次性迁移所有内容。最成功的迁移遵循绞杀者模式(strangler fig pattern)——新服务使用 gRPC/Protobuf,现有服务逐步迁移,REST/JSON 接口通过 gRPC-Gateway 或 Connect RPC 的多协议支持作为兼容层维护。

常见问题

Protobuf 总是比 JSON 快吗?

在大多数情况下是的。Protobuf 的序列化/反序列化速度通常比 JSON 快 2–5 倍,载荷小 3–10 倍。但在 JavaScript 环境中,V8 高度优化的 JSON.parse() 对于小载荷可以与 Protobuf.js 竞争。对于大型数据集和强类型语言(Go、Java、C++),Protobuf 的性能优势显著。请始终使用你的具体数据和运行时进行基准测试。

可以不用 gRPC 而单独使用 Protobuf 吗?

完全可以。Protobuf 只是一种序列化格式——可以独立于 gRPC 使用。你可以序列化 Protobuf 消息并通过 REST API、消息队列(Kafka、RabbitMQ)、数据库或文件发送。许多团队在 Kafka 消息中使用 Protobuf,同时保持 HTTP API 使用 REST/JSON。

如何调试二进制 Protobuf 数据?

有几种方法:(1) 使用 protoc --decode 将二进制数据解码为文本格式;(2) 使用 grpcurl 或 grpcui 交互式调试 gRPC 服务;(3) 使用 Protobuf 的 JSON 映射(Go 中的 jsonpb,Java 中的 MessageToJson)转换为可读 JSON 用于日志;(4) Postman 支持导入 .proto 文件并显示解码后的响应。在生产环境中,记录 Protobuf 消息的 JSON 表示以便于可观测性。

gRPC 能在 Web 浏览器中工作吗?

不能直接使用。浏览器不支持 gRPC 所需的 HTTP/2 trailers。你有三个选择:(1) <strong>grpc-web</strong>——通过 Envoy 代理工作的协议;(2) <strong>Connect RPC</strong>——支持 gRPC、gRPC-web 和新的 Connect 协议的现代框架,无需代理即可在浏览器中原生工作;(3) <strong>gRPC-Gateway</strong>——在 gRPC 服务前面生成 REST/JSON 反向代理。Connect RPC 越来越成为新项目的推荐方式。

应该使用 proto2 还是 proto3?

所有新项目使用 <strong>proto3</strong>。Proto3 更简单(无 required 字段、无默认值、无 extensions),有更好的 JSON 映射,是许多新工具(Connect RPC、Buf)唯一支持的版本。Proto2 仍然受支持,但主要用于 Google 内部的遗留代码。Proto2 一些人怀念的主要特性是 required 字段,但 Protobuf 团队已确定 required 字段在实践中造成的问题比解决的问题更多。

使用我们的免费 JSON 转 Protobuf 工具进行格式转换 →

使用我们的免费 JSON 格式化工具格式化和验证 JSON →

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

PBJSON to Protobuf{ }JSON FormatterGQLJSON to GraphQL

相关文章

XML vs JSON:何时使用哪个 — 开发者完整对比

XML 和 JSON 的全面数据交换对比。语法、解析、大小、可读性、Schema 验证和各格式的实际使用场景。

REST API 最佳实践:2026 完整指南

学习 REST API 设计最佳实践,包括命名规范、错误处理、认证、分页、版本控制和安全头。