在现代Web开发中,JSON已成为数据交换的标准格式。无论是构建RESTful API还是处理前后端数据交互,掌握JSON的编码解码技术都是必不可少的技能。本文将深入探讨Go语言中JSON处理的各种技巧,从基础操作到高级应用,帮你构建健壮的JSON API系统。
1 JSON编码解码基础
Go语言的encoding/json包为我们提供了强大的JSON处理能力。让我们从最基础的操作开始,逐步深入了解其工作原理。
1.1 基础数据类型的JSON处理
go
package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
// 演示基础类型的JSON编码解码
func basicJSONDemo() {
// 基础类型编码
data := map[string]interface{}{
"name": "张三",
"age": 25,
"is_active": true,
"score": 98.5,
"tags": []string{"Go", "Web", "API"},
"created_at": time.Now(),
}
// 编码为JSON
jsonBytes, err := json.Marshal(data)
if err != nil {
log.Fatal("JSON编码失败:", err)
}
fmt.Printf("编码结果: %s\n\n", jsonBytes)
// 解码JSON
var decoded map[string]interface{}
if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
log.Fatal("JSON解码失败:", err)
}
fmt.Println("解码结果:")
for key, value := range decoded {
fmt.Printf(" %s: %v (类型: %T)\n", key, value, value)
}
}
这个基础示例展示了Go中JSON处理的核心概念。需要注意的是,解码后的数字类型会变成float64,这是JSON规范的限制。
1.2 结构体与JSON的映射关系
go
import (
"strings"
)
// 用户信息结构体
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age,omitempty"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Profile *Profile `json:"profile,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// 用户档案结构体
type Profile struct {
Avatar string `json:"avatar,omitempty"`
Bio string `json:"bio,omitempty"`
Website string `json:"website,omitempty"`
Location string `json:"location,omitempty"`
}
// 结构体JSON处理演示
func structJSONDemo() {
// 创建用户对象
now := time.Now()
user := User{
ID: 1001,
Name: "李四",
Email: "lisi@example.com",
Age: 28,
IsActive: true,
CreatedAt: now,
UpdatedAt: &now,
Profile: &Profile{
Avatar: "https://example.com/avatar.jpg",
Bio: "Go语言开发者",
Website: "https://lisi.dev",
Location: "北京",
},
Tags: []string{"Go", "Docker", "Kubernetes"},
Metadata: map[string]string{
"department": "技术部",
"level": "高级工程师",
},
}
// 编码为JSON(格式化输出)
jsonBytes, err := json.MarshalIndent(user, "", " ")
if err != nil {
log.Fatal("JSON编码失败:", err)
}
fmt.Printf("用户JSON数据:\n%s\n\n", jsonBytes)
// 从JSON解码
var decodedUser User
if err := json.Unmarshal(jsonBytes, &decodedUser); err != nil {
log.Fatal("JSON解码失败:", err)
}
fmt.Printf("解码后的用户: %+v\n", decodedUser)
}
1.3 JSON标签的高级用法
go
// 商品信息结构体(展示各种JSON标签用法)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
// omitempty: 空值时不包含在JSON中
Description string `json:"description,omitempty"`
// 使用不同的JSON字段名
SKU string `json:"sku_code"`
// 忽略字段(不会出现在JSON中)
InternalCode string `json:"-"`
// 字符串形式的数字
Weight int `json:"weight,string"`
// 嵌入结构体
Category Category `json:"category"`
// 指针类型,支持null值
Discount *float64 `json:"discount,omitempty"`
// 自定义时间格式
LaunchDate CustomTime `json:"launch_date"`
}
type Category struct {
ID int `json:"id"`
Name string `json:"name"`
}
// 自定义时间类型,实现自定义JSON编码解码
type CustomTime struct {
time.Time
}
// 自定义JSON编码
func (ct CustomTime) MarshalJSON() ([]byte, error) {
formatted := ct.Time.Format("2006-01-02")
return json.Marshal(formatted)
}
// 自定义JSON解码
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
var dateStr string
if err := json.Unmarshal(data, &dateStr); err != nil {
return err
}
parsedTime, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return err
}
ct.Time = parsedTime
return nil
}
// JSON标签演示
func jsonTagsDemo() {
discount := 15.5
product := Product{
ID: 2001,
Name: "Go编程实战",
Price: 89.99,
Description: "深入学习Go语言的实战教程",
SKU: "BOOK-GO-001",
InternalCode: "INTERNAL-12345", // 这个字段不会出现在JSON中
Weight: 500,
Category: Category{
ID: 10,
Name: "编程书籍",
},
Discount: &discount,
LaunchDate: CustomTime{time.Now()},
}
// 编码为JSON
jsonBytes, err := json.MarshalIndent(product, "", " ")
if err != nil {
log.Fatal("JSON编码失败:", err)
}
fmt.Printf("商品JSON数据:\n%s\n\n", jsonBytes)
// 解码测试
var decodedProduct Product
if err := json.Unmarshal(jsonBytes, &decodedProduct); err != nil {
log.Fatal("JSON解码失败:", err)
}
fmt.Printf("解码后的商品: %+v\n", decodedProduct)
}
2 高级JSON处理技术
2.1 动态JSON处理
在实际开发中,我们经常需要处理结构不固定的JSON数据。Go提供了多种方式来处理这种情况。
go
import (
"reflect"
"strconv"
)
// JSON处理器
type JSONProcessor struct {
data map[string]interface{}
}
func NewJSONProcessor(jsonData []byte) (*JSONProcessor, error) {
var data map[string]interface{}
if err := json.Unmarshal(jsonData, &data); err != nil {
return nil, fmt.Errorf("JSON解析失败: %w", err)
}
return &JSONProcessor{data: data}, nil
}
// 获取字符串值(带默认值)
func (jp *JSONProcessor) GetString(key, defaultValue string) string {
if value, exists := jp.data[key]; exists {
if str, ok := value.(string); ok {
return str
}
}
return defaultValue
}
// 获取整数值(带类型转换)
func (jp *JSONProcessor) GetInt(key string, defaultValue int) int {
if value, exists := jp.data[key]; exists {
switch v := value.(type) {
case int:
return v
case float64:
return int(v)
case string:
if num, err := strconv.Atoi(v); err == nil {
return num
}
}
}
return defaultValue
}
// 获取浮点数值
func (jp *JSONProcessor) GetFloat(key string, defaultValue float64) float64 {
if value, exists := jp.data[key]; exists {
switch v := value.(type) {
case float64:
return v
case int:
return float64(v)
case string:
if num, err := strconv.ParseFloat(v, 64); err == nil {
return num
}
}
}
return defaultValue
}
// 获取布尔值
func (jp *JSONProcessor) GetBool(key string, defaultValue bool) bool {
if value, exists := jp.data[key]; exists {
switch v := value.(type) {
case bool:
return v
case string:
return strings.ToLower(v) == "true"
case float64:
return v != 0
}
}
return defaultValue
}
// 获取数组值
func (jp *JSONProcessor) GetArray(key string) []interface{} {
if value, exists := jp.data[key]; exists {
if arr, ok := value.([]interface{}); ok {
return arr
}
}
return nil
}
// 获取嵌套对象
func (jp *JSONProcessor) GetObject(key string) *JSONProcessor {
if value, exists := jp.data[key]; exists {
if obj, ok := value.(map[string]interface{}); ok {
return &JSONProcessor{data: obj}
}
}
return nil
}
// 设置值
func (jp *JSONProcessor) Set(key string, value interface{}) {
jp.data[key] = value
}
// 删除键
func (jp *JSONProcessor) Delete(key string) {
delete(jp.data, key)
}
// 检查键是否存在
func (jp *JSONProcessor) Has(key string) bool {
_, exists := jp.data[key]
return exists
}
// 获取所有键
func (jp *JSONProcessor) Keys() []string {
keys := make([]string, 0, len(jp.data))
for key := range jp.data {
keys = append(keys, key)
}
return keys
}
// 转换为JSON字节
func (jp *JSONProcessor) ToJSON() ([]byte, error) {
return json.Marshal(jp.data)
}
// 转换为格式化JSON
func (jp *JSONProcessor) ToJSONIndent() ([]byte, error) {
return json.MarshalIndent(jp.data, "", " ")
}
// 动态JSON处理演示
func dynamicJSONDemo() {
// 模拟接收到的JSON数据
jsonData := `{
"user_id": "12345",
"name": "王五",
"age": "30",
"is_premium": "true",
"scores": [85, 92, 78],
"profile": {
"avatar": "avatar.jpg",
"bio": "软件工程师"
},
"metadata": {
"last_login": "2024-01-15T10:30:00Z",
"login_count": 156
}
}`
// 创建JSON处理器
processor, err := NewJSONProcessor([]byte(jsonData))
if err != nil {
log.Fatal("创建JSON处理器失败:", err)
}
// 提取各种类型的数据
userID := processor.GetString("user_id", "")
name := processor.GetString("name", "未知用户")
age := processor.GetInt("age", 0)
isPremium := processor.GetBool("is_premium", false)
scores := processor.GetArray("scores")
fmt.Printf("用户ID: %s\n", userID)
fmt.Printf("姓名: %s\n", name)
fmt.Printf("年龄: %d\n", age)
fmt.Printf("高级用户: %t\n", isPremium)
fmt.Printf("分数: %v\n", scores)
// 处理嵌套对象
if profile := processor.GetObject("profile"); profile != nil {
avatar := profile.GetString("avatar", "")
bio := profile.GetString("bio", "")
fmt.Printf("头像: %s\n", avatar)
fmt.Printf("简介: %s\n", bio)
}
// 修改数据
processor.Set("last_updated", time.Now().Format(time.RFC3339))
processor.Set("processed", true)
// 输出修改后的JSON
if modifiedJSON, err := processor.ToJSONIndent(); err == nil {
fmt.Printf("\n修改后的JSON:\n%s\n", modifiedJSON)
}
}
2.2 JSON流式处理
对于大型JSON数据,流式处理可以显著减少内存使用。
go
import (
"io"
"strings"
)
// JSON流处理器
type JSONStreamProcessor struct {
decoder *json.Decoder
encoder *json.Encoder
}
func NewJSONStreamProcessor(reader io.Reader, writer io.Writer) *JSONStreamProcessor {
return &JSONStreamProcessor{
decoder: json.NewDecoder(reader),
encoder: json.NewEncoder(writer),
}
}
// 处理JSON数组流
func (jsp *JSONStreamProcessor) ProcessArray(handler func(interface{}) error) error {
// 读取数组开始标记
token, err := jsp.decoder.Token()
if err != nil {
return err
}
if delim, ok := token.(json.Delim); !ok || delim != '[' {
return fmt.Errorf("期望数组开始标记,得到: %v", token)
}
// 处理数组中的每个元素
for jsp.decoder.More() {
var element interface{}
if err := jsp.decoder.Decode(&element); err != nil {
return err
}
if err := handler(element); err != nil {
return err
}
}
// 读取数组结束标记
token, err = jsp.decoder.Token()
if err != nil {
return err
}
if delim, ok := token.(json.Delim); !ok || delim != ']' {
return fmt.Errorf("期望数组结束标记,得到: %v", token)
}
return nil
}
// 处理JSON对象流
func (jsp *JSONStreamProcessor) ProcessObject(handler func(string, interface{}) error) error {
// 读取对象开始标记
token, err := jsp.decoder.Token()
if err != nil {
return err
}
if delim, ok := token.(json.Delim); !ok || delim != '{' {
return fmt.Errorf("期望对象开始标记,得到: %v", token)
}
// 处理对象中的每个键值对
for jsp.decoder.More() {
// 读取键
keyToken, err := jsp.decoder.Token()
if err != nil {
return err
}
key, ok := keyToken.(string)
if !ok {
return fmt.Errorf("期望字符串键,得到: %v", keyToken)
}
// 读取值
var value interface{}
if err := jsp.decoder.Decode(&value); err != nil {
return err
}
if err := handler(key, value); err != nil {
return err
}
}
// 读取对象结束标记
token, err = jsp.decoder.Token()
if err != nil {
return err
}
if delim, ok := token.(json.Delim); !ok || delim != '}' {
return fmt.Errorf("期望对象结束标记,得到: %v", token)
}
return nil
}
// 编码并写入数据
func (jsp *JSONStreamProcessor) Encode(data interface{}) error {
return jsp.encoder.Encode(data)
}
// 流式处理演示
func streamProcessingDemo() {
// 模拟大型JSON数组
jsonArray := `[
{"id": 1, "name": "用户1", "score": 85},
{"id": 2, "name": "用户2", "score": 92},
{"id": 3, "name": "用户3", "score": 78},
{"id": 4, "name": "用户4", "score": 96},
{"id": 5, "name": "用户5", "score": 88}
]`
reader := strings.NewReader(jsonArray)
var output strings.Builder
processor := NewJSONStreamProcessor(reader, &output)
fmt.Println("流式处理JSON数组:")
// 处理数组中的每个用户
err := processor.ProcessArray(func(element interface{}) error {
if user, ok := element.(map[string]interface{}); ok {
id := user["id"]
name := user["name"]
score := user["score"]
fmt.Printf("处理用户: ID=%v, 姓名=%v, 分数=%v\n", id, name, score)
// 可以在这里进行数据转换、验证等操作
if scoreFloat, ok := score.(float64); ok && scoreFloat >= 90 {
fmt.Printf(" -> 高分用户!\n")
}
}
return nil
})
if err != nil {
log.Fatal("流式处理失败:", err)
}
}
3 RESTful API设计与实现
3.1 标准化的API响应结构
go
import (
"net/http"
)
// 标准API响应结构
type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
Meta *Meta `json:"meta,omitempty"`
Timestamp int64 `json:"timestamp"`
}
// API错误结构
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// 元数据结构(用于分页等)
type Meta struct {
Page int `json:"page,omitempty"`
PageSize int `json:"page_size,omitempty"`
Total int `json:"total,omitempty"`
TotalPages int `json:"total_pages,omitempty"`
}
// API响应构建器
type ResponseBuilder struct {
response *APIResponse
}
func NewResponseBuilder() *ResponseBuilder {
return &ResponseBuilder{
response: &APIResponse{
Timestamp: time.Now().Unix(),
},
}
}
// 成功响应
func (rb *ResponseBuilder) Success(data interface{}) *ResponseBuilder {
rb.response.Success = true
rb.response.Data = data
return rb
}
// 错误响应
func (rb *ResponseBuilder) Error(code, message, details string) *ResponseBuilder {
rb.response.Success = false
rb.response.Error = &APIError{
Code: code,
Message: message,
Details: details,
}
return rb
}
// 设置消息
func (rb *ResponseBuilder) Message(message string) *ResponseBuilder {
rb.response.Message = message
return rb
}
// 设置元数据
func (rb *ResponseBuilder) Meta(meta *Meta) *ResponseBuilder {
rb.response.Meta = meta
return rb
}
// 构建响应
func (rb *ResponseBuilder) Build() *APIResponse {
return rb.response
}
// JSON响应助手
func WriteJSONResponse(w http.ResponseWriter, statusCode int, response *APIResponse) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("JSON编码失败: %v", err)
http.Error(w, "内部服务器错误", http.StatusInternalServerError)
}
}
// 便捷的响应函数
func WriteSuccess(w http.ResponseWriter, data interface{}) {
response := NewResponseBuilder().Success(data).Build()
WriteJSONResponse(w, http.StatusOK, response)
}
func WriteError(w http.ResponseWriter, statusCode int, code, message, details string) {
response := NewResponseBuilder().Error(code, message, details).Build()
WriteJSONResponse(w, statusCode, response)
}
func WriteCreated(w http.ResponseWriter, data interface{}) {
response := NewResponseBuilder().Success(data).Message("创建成功").Build()
WriteJSONResponse(w, http.StatusCreated, response)
}
func WriteUpdated(w http.ResponseWriter, data interface{}) {
response := NewResponseBuilder().Success(data).Message("更新成功").Build()
WriteJSONResponse(w, http.StatusOK, response)
}
func WriteDeleted(w http.ResponseWriter) {
response := NewResponseBuilder().Success(nil).Message("删除成功").Build()
WriteJSONResponse(w, http.StatusOK, response)
}
3.2 用户管理API实现
go
// 用户管理API
type UserAPI struct {
users map[int]*User // 简化存储,实际项目中应使用数据库
nextID int
}
func NewUserAPI() *UserAPI {
return &UserAPI{
users: make(map[int]*User),
nextID: 1,
}
}
// 创建用户
func (api *UserAPI) CreateUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
return
}
// 解析请求体
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
return
}
// 验证必需字段
if user.Name == "" {
WriteError(w, http.StatusBadRequest, "MISSING_NAME", "用户名不能为空", "")
return
}
if user.Email == "" {
WriteError(w, http.StatusBadRequest, "MISSING_EMAIL", "邮箱不能为空", "")
return
}
// 检查邮箱是否已存在
for _, existingUser := range api.users {
if existingUser.Email == user.Email {
WriteError(w, http.StatusConflict, "EMAIL_EXISTS", "邮箱已存在", "")
return
}
}
// 设置用户ID和创建时间
user.ID = api.nextID
api.nextID++
user.CreatedAt = time.Now()
user.IsActive = true
// 存储用户
api.users[user.ID] = &user
WriteCreated(w, user)
}
// 获取用户列表
func (api *UserAPI) GetUsers(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
return
}
// 解析查询参数
query := r.URL.Query()
page := 1
pageSize := 10
if pageStr := query.Get("page"); pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
page = p
}
}
if sizeStr := query.Get("page_size"); sizeStr != "" {
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
pageSize = s
}
}
// 获取所有用户
allUsers := make([]*User, 0, len(api.users))
for _, user := range api.users {
allUsers = append(allUsers, user)
}
// 计算分页
total := len(allUsers)
start := (page - 1) * pageSize
end := start + pageSize
if start >= total {
allUsers = []*User{}
} else {
if end > total {
end = total
}
allUsers = allUsers[start:end]
}
// 构建响应
meta := &Meta{
Page: page,
PageSize: pageSize,
Total: total,
TotalPages: (total + pageSize - 1) / pageSize,
}
response := NewResponseBuilder().Success(allUsers).Meta(meta).Build()
WriteJSONResponse(w, http.StatusOK, response)
}
// 获取单个用户
func (api *UserAPI) GetUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
return
}
// 从URL路径提取用户ID(简化实现)
path := r.URL.Path
parts := strings.Split(path, "/")
if len(parts) < 3 {
WriteError(w, http.StatusBadRequest, "INVALID_PATH", "无效的路径", "")
return
}
userID, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_USER_ID", "无效的用户ID", "")
return
}
// 查找用户
user, exists := api.users[userID]
if !exists {
WriteError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在", "")
return
}
WriteSuccess(w, user)
}
// 更新用户
func (api *UserAPI) UpdateUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
return
}
// 提取用户ID
path := r.URL.Path
parts := strings.Split(path, "/")
if len(parts) < 3 {
WriteError(w, http.StatusBadRequest, "INVALID_PATH", "无效的路径", "")
return
}
userID, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_USER_ID", "无效的用户ID", "")
return
}
// 检查用户是否存在
existingUser, exists := api.users[userID]
if !exists {
WriteError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在", "")
return
}
// 解析更新数据
var updateData User
if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
return
}
// 更新用户信息(保留原有的ID和创建时间)
updateData.ID = existingUser.ID
updateData.CreatedAt = existingUser.CreatedAt
now := time.Now()
updateData.UpdatedAt = &now
// 验证邮箱唯一性
if updateData.Email != existingUser.Email {
for id, user := range api.users {
if id != userID && user.Email == updateData.Email {
WriteError(w, http.StatusConflict, "EMAIL_EXISTS", "邮箱已存在", "")
return
}
}
}
// 保存更新
api.users[userID] = &updateData
WriteUpdated(w, updateData)
}
// 删除用户
func (api *UserAPI) DeleteUser(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
return
}
// 提取用户ID
path := r.URL.Path
parts := strings.Split(path, "/")
if len(parts) < 3 {
WriteError(w, http.StatusBadRequest, "INVALID_PATH", "无效的路径", "")
return
}
userID, err := strconv.Atoi(parts[len(parts)-1])
if err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_USER_ID", "无效的用户ID", "")
return
}
// 检查用户是否存在
if _, exists := api.users[userID]; !exists {
WriteError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在", "")
return
}
// 删除用户
delete(api.users, userID)
WriteDeleted(w)
}
4 内容协商与格式处理
4.1 内容协商中间件
go
import (
"compress/gzip"
"strings"
)
// 支持的内容类型
const (
ContentTypeJSON = "application/json"
ContentTypeXML = "application/xml"
ContentTypeText = "text/plain"
ContentTypeHTML = "text/html"
)
// 内容协商中间件
func ContentNegotiationMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 解析Accept头
acceptHeader := r.Header.Get("Accept")
contentType := negotiateContentType(acceptHeader)
// 设置响应内容类型
w.Header().Set("Content-Type", contentType)
// 将协商结果存储到上下文中
ctx := context.WithValue(r.Context(), "content_type", contentType)
next(w, r.WithContext(ctx))
}
}
// 协商内容类型
func negotiateContentType(acceptHeader string) string {
if acceptHeader == "" {
return ContentTypeJSON // 默认返回JSON
}
// 解析Accept头中的媒体类型
mediaTypes := parseAcceptHeader(acceptHeader)
// 按优先级排序并选择支持的类型
for _, mediaType := range mediaTypes {
switch {
case strings.Contains(mediaType.Type, "application/json"):
return ContentTypeJSON
case strings.Contains(mediaType.Type, "application/xml"):
return ContentTypeXML
case strings.Contains(mediaType.Type, "text/plain"):
return ContentTypeText
case strings.Contains(mediaType.Type, "text/html"):
return ContentTypeHTML
}
}
return ContentTypeJSON // 默认返回JSON
}
// 媒体类型结构
type MediaType struct {
Type string
Quality float64
Params map[string]string
}
// 解析Accept头
func parseAcceptHeader(acceptHeader string) []MediaType {
var mediaTypes []MediaType
parts := strings.Split(acceptHeader, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
mediaType := parseMediaType(part)
mediaTypes = append(mediaTypes, mediaType)
}
// 按质量值排序(简化实现)
// 实际项目中应该实现完整的排序逻辑
return mediaTypes
}
// 解析单个媒体类型
func parseMediaType(mediaTypeStr string) MediaType {
mediaType := MediaType{
Quality: 1.0,
Params: make(map[string]string),
}
parts := strings.Split(mediaTypeStr, ";")
mediaType.Type = strings.TrimSpace(parts[0])
// 解析参数
for i := 1; i < len(parts); i++ {
param := strings.TrimSpace(parts[i])
if strings.HasPrefix(param, "q=") {
if quality, err := strconv.ParseFloat(param[2:], 64); err == nil {
mediaType.Quality = quality
}
} else {
keyValue := strings.SplitN(param, "=", 2)
if len(keyValue) == 2 {
mediaType.Params[keyValue[0]] = keyValue[1]
}
}
}
return mediaType
}
// 压缩中间件
func CompressionMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 检查客户端是否支持gzip压缩
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
next(w, r)
return
}
// 设置压缩响应头
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Vary", "Accept-Encoding")
// 创建gzip写入器
gzipWriter := gzip.NewWriter(w)
defer gzipWriter.Close()
// 包装响应写入器
gzipResponseWriter := &GzipResponseWriter{
ResponseWriter: w,
Writer: gzipWriter,
}
next(gzipResponseWriter, r)
}
}
// Gzip响应写入器
type GzipResponseWriter struct {
http.ResponseWriter
Writer io.Writer
}
func (grw *GzipResponseWriter) Write(data []byte) (int, error) {
return grw.Writer.Write(data)
}
// 多格式响应处理器
func MultiFormatResponse(w http.ResponseWriter, r *http.Request, data interface{}) {
contentType := r.Context().Value("content_type").(string)
switch contentType {
case ContentTypeJSON:
writeJSONResponse(w, data)
case ContentTypeXML:
writeXMLResponse(w, data)
case ContentTypeText:
writeTextResponse(w, data)
case ContentTypeHTML:
writeHTMLResponse(w, data)
default:
writeJSONResponse(w, data)
}
}
func writeJSONResponse(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(data)
}
func writeXMLResponse(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
// 这里需要实现XML编码逻辑
fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<data>%v</data>", data)
}
func writeTextResponse(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "%v", data)
}
func writeHTMLResponse(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<html><body><pre>%v</pre></body></html>", data)
}
5 完整示例:图书管理API
让我们通过一个完整的图书管理API来综合运用本章学到的所有技术。
go
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
// 图书结构体
type Book struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
ISBN string `json:"isbn"`
Price float64 `json:"price"`
Category string `json:"category"`
Description string `json:"description,omitempty"`
PublishedAt CustomTime `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// 图书管理API
type BookAPI struct {
books map[int]*Book
nextID int
}
func NewBookAPI() *BookAPI {
api := &BookAPI{
books: make(map[int]*Book),
nextID: 1,
}
// 添加一些示例数据
api.seedData()
return api
}
// 添加示例数据
func (api *BookAPI) seedData() {
books := []*Book{
{
Title: "Go语言编程",
Author: "许式伟",
ISBN: "978-7-115-29036-9",
Price: 59.00,
Category: "编程",
Description: "Go语言编程入门与实战",
PublishedAt: CustomTime{time.Date(2012, 8, 1, 0, 0, 0, 0, time.UTC)},
},
{
Title: "深入理解计算机系统",
Author: "Randal E. Bryant",
ISBN: "978-7-111-54493-7",
Price: 139.00,
Category: "计算机科学",
Description: "计算机系统的经典教材",
PublishedAt: CustomTime{time.Date(2016, 11, 1, 0, 0, 0, 0, time.UTC)},
},
}
for _, book := range books {
book.ID = api.nextID
api.nextID++
book.CreatedAt = time.Now()
api.books[book.ID] = book
}
}
// 设置路由
func (api *BookAPI) SetupRoutes() {
// 应用中间件
http.HandleFunc("/api/books",
CompressionMiddleware(
ContentNegotiationMiddleware(api.handleBooks)))
http.HandleFunc("/api/books/",
CompressionMiddleware(
ContentNegotiationMiddleware(api.handleBook)))
// 健康检查端点
http.HandleFunc("/health", api.healthCheck)
// API文档端点
http.HandleFunc("/api/docs", api.apiDocs)
}
// 处理图书集合请求
func (api *BookAPI) handleBooks(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
api.getBooks(w, r)
case http.MethodPost:
api.createBook(w, r)
default:
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
}
}
// 处理单个图书请求
func (api *BookAPI) handleBook(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
api.getBook(w, r)
case http.MethodPut:
api.updateBook(w, r)
case http.MethodDelete:
api.deleteBook(w, r)
default:
WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
}
}
// 获取图书列表
func (api *BookAPI) getBooks(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
// 解析查询参数
category := query.Get("category")
author := query.Get("author")
page := 1
pageSize := 10
if pageStr := query.Get("page"); pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
page = p
}
}
if sizeStr := query.Get("page_size"); sizeStr != "" {
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
pageSize = s
}
}
// 过滤图书
var filteredBooks []*Book
for _, book := range api.books {
if category != "" && book.Category != category {
continue
}
if author != "" && !strings.Contains(strings.ToLower(book.Author), strings.ToLower(author)) {
continue
}
filteredBooks = append(filteredBooks, book)
}
// 分页处理
total := len(filteredBooks)
start := (page - 1) * pageSize
end := start + pageSize
if start >= total {
filteredBooks = []*Book{}
} else {
if end > total {
end = total
}
filteredBooks = filteredBooks[start:end]
}
// 构建响应
meta := &Meta{
Page: page,
PageSize: pageSize,
Total: total,
TotalPages: (total + pageSize - 1) / pageSize,
}
response := NewResponseBuilder().Success(filteredBooks).Meta(meta).Build()
WriteJSONResponse(w, http.StatusOK, response)
}
// 创建图书
func (api *BookAPI) createBook(w http.ResponseWriter, r *http.Request) {
var book Book
if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
return
}
// 验证必需字段
if book.Title == "" {
WriteError(w, http.StatusBadRequest, "MISSING_TITLE", "书名不能为空", "")
return
}
if book.Author == "" {
WriteError(w, http.StatusBadRequest, "MISSING_AUTHOR", "作者不能为空", "")
return
}
if book.ISBN == "" {
WriteError(w, http.StatusBadRequest, "MISSING_ISBN", "ISBN不能为空", "")
return
}
// 检查ISBN是否已存在
for _, existingBook := range api.books {
if existingBook.ISBN == book.ISBN {
WriteError(w, http.StatusConflict, "ISBN_EXISTS", "ISBN已存在", "")
return
}
}
// 设置图书信息
book.ID = api.nextID
api.nextID++
book.CreatedAt = time.Now()
// 存储图书
api.books[book.ID] = &book
WriteCreated(w, book)
}
// 获取单个图书
func (api *BookAPI) getBook(w http.ResponseWriter, r *http.Request) {
bookID := api.extractBookID(r.URL.Path)
if bookID == 0 {
WriteError(w, http.StatusBadRequest, "INVALID_BOOK_ID", "无效的图书ID", "")
return
}
book, exists := api.books[bookID]
if !exists {
WriteError(w, http.StatusNotFound, "BOOK_NOT_FOUND", "图书不存在", "")
return
}
WriteSuccess(w, book)
}
// 更新图书
func (api *BookAPI) updateBook(w http.ResponseWriter, r *http.Request) {
bookID := api.extractBookID(r.URL.Path)
if bookID == 0 {
WriteError(w, http.StatusBadRequest, "INVALID_BOOK_ID", "无效的图书ID", "")
return
}
existingBook, exists := api.books[bookID]
if !exists {
WriteError(w, http.StatusNotFound, "BOOK_NOT_FOUND", "图书不存在", "")
return
}
var updateData Book
if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {
WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
return
}
// 保留原有信息
updateData.ID = existingBook.ID
updateData.CreatedAt = existingBook.CreatedAt
now := time.Now()
updateData.UpdatedAt = &now
// 检查ISBN唯一性
if updateData.ISBN != existingBook.ISBN {
for id, book := range api.books {
if id != bookID && book.ISBN == updateData.ISBN {
WriteError(w, http.StatusConflict, "ISBN_EXISTS", "ISBN已存在", "")
return
}
}
}
// 保存更新
api.books[bookID] = &updateData
WriteUpdated(w, updateData)
}
// 删除图书
func (api *BookAPI) deleteBook(w http.ResponseWriter, r *http.Request) {
bookID := api.extractBookID(r.URL.Path)
if bookID == 0 {
WriteError(w, http.StatusBadRequest, "INVALID_BOOK_ID", "无效的图书ID", "")
return
}
if _, exists := api.books[bookID]; !exists {
WriteError(w, http.StatusNotFound, "BOOK_NOT_FOUND", "图书不存在", "")
return
}
delete(api.books, bookID)
WriteDeleted(w)
}
// 从URL路径提取图书ID
func (api *BookAPI) extractBookID(path string) int {
parts := strings.Split(path, "/")
if len(parts) < 4 {
return 0
}
idStr := parts[len(parts)-1]
if id, err := strconv.Atoi(idStr); err == nil {
return id
}
return 0
}
// 健康检查
func (api *BookAPI) healthCheck(w http.ResponseWriter, r *http.Request) {
health := map[string]interface{}{
"status": "healthy",
"timestamp": time.Now().Unix(),
"version": "1.0.0",
"books_count": len(api.books),
}
WriteSuccess(w, health)
}
// API文档
func (api *BookAPI) apiDocs(w http.ResponseWriter, r *http.Request) {
docs := map[string]interface{}{
"title": "图书管理API",
"version": "1.0.0",
"description": "简单的图书管理RESTful API",
"endpoints": map[string]interface{}{
"GET /api/books": "获取图书列表",
"POST /api/books": "创建新图书",
"GET /api/books/{id}": "获取指定图书",
"PUT /api/books/{id}": "更新指定图书",
"DELETE /api/books/{id}": "删除指定图书",
"GET /health": "健康检查",
},
"query_parameters": map[string]string{
"page": "页码(默认1)",
"page_size": "每页大小(默认10,最大100)",
"category": "图书分类过滤",
"author": "作者名称过滤",
},
}
WriteSuccess(w, docs)
}
// 主函数
func main() {
// 创建API实例
bookAPI := NewBookAPI()
bookAPI.SetupRoutes()
// 创建HTTP服务器
server := &http.Server{
Addr: ":8080",
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// 启动服务器
go func() {
fmt.Println("图书管理API服务启动在 http://localhost:8080")
fmt.Println("API端点:")
fmt.Println(" GET /api/books - 获取图书列表")
fmt.Println(" POST /api/books - 创建新图书")
fmt.Println(" GET /api/books/{id} - 获取指定图书")
fmt.Println(" PUT /api/books/{id} - 更新指定图书")
fmt.Println(" DELETE /api/books/{id} - 删除指定图书")
fmt.Println(" GET /health - 健康检查")
fmt.Println(" GET /api/docs - API文档")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("服务器启动失败:", err)
}
}()
// 优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("正在关闭服务器...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("服务器关闭失败:", err)
}
fmt.Println("服务器已关闭")
}
6 测试与调试
6.1 API测试示例
go
// 测试用例
func testBookAPI() {
// 测试创建图书
fmt.Println("=== 测试创建图书 ===")
createBookJSON := `{
"title": "Go Web编程实战",
"author": "张三",
"isbn": "978-7-111-12345-6",
"price": 79.99,
"category": "编程",
"description": "Go语言Web开发完整指南",
"published_at": "2024-01-15"
}`
resp, err := http.Post("http://localhost:8080/api/books",
"application/json",
strings.NewReader(createBookJSON))
if err != nil {
log.Fatal("请求失败:", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应: %s\n\n", body)
// 测试获取图书列表
fmt.Println("=== 测试获取图书列表 ===")
resp, err = http.Get("http://localhost:8080/api/books?page=1&page_size=5")
if err != nil {
log.Fatal("请求失败:", err)
}
defer resp.Body.Close()
body, _ = io.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应: %s\n\n", body)
// 测试获取单个图书
fmt.Println("=== 测试获取单个图书 ===")
resp, err = http.Get("http://localhost:8080/api/books/1")
if err != nil {
log.Fatal("请求失败:", err)
}
defer resp.Body.Close()
body, _ = io.ReadAll(resp.Body)
fmt.Printf("状态码: %d\n", resp.StatusCode)
fmt.Printf("响应: %s\n\n", body)
}
通过这篇文章,我们深入学习了Go语言中JSON API的设计与实现。从基础的JSON编码解码到高级的内容协商,从简单的数据处理到完整的RESTful API系统,这些技能为构建现代Web应用奠定了坚实基础。
在实际项目中,建议根据业务需求选择合适的JSON处理方案,并建立统一的API设计规范。合理的API设计不仅能提升开发效率,还能为系统的扩展和维护提供便利。