Go Web 编程快速入门 08 - JSON API:编码、解码与内容协商
在现代Web开发中,JSON已成为数据交换的标准格式。无论是构建RESTful API还是处理前后端数据交互,掌握JSON的编码解码技术都是必不可少的技能。本文将深入探讨Go语言中JSON处理的各种技巧,从基础操作到高级应用,帮你构建健壮的JSON API系统。
1 JSON编码解码基础
Go语言的encoding/json包为我们提供了强大的JSON处理能力。让我们从最基础的操作开始,逐步深入了解其工作原理。
1.1 基础数据类型的JSON处理
package mainimport ("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(),}// 编码为JSONjsonBytes, err := json.Marshal(data)if err != nil {log.Fatal("JSON编码失败:", err)}fmt.Printf("编码结果: %s\n\n", jsonBytes)// 解码JSONvar 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的映射关系
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 Userif err := json.Unmarshal(jsonBytes, &decodedUser); err != nil {log.Fatal("JSON解码失败:", err)}fmt.Printf("解码后的用户: %+v\n", decodedUser)
}
1.3 JSON标签的高级用法
// 商品信息结构体(展示各种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 stringif err := json.Unmarshal(data, &dateStr); err != nil {return err}parsedTime, err := time.Parse("2006-01-02", dateStr)if err != nil {return err}ct.Time = parsedTimereturn nil
}// JSON标签演示
func jsonTagsDemo() {discount := 15.5product := 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()},}// 编码为JSONjsonBytes, err := json.MarshalIndent(product, "", " ")if err != nil {log.Fatal("JSON编码失败:", err)}fmt.Printf("商品JSON数据:\n%s\n\n", jsonBytes)// 解码测试var decodedProduct Productif err := json.Unmarshal(jsonBytes, &decodedProduct); err != nil {log.Fatal("JSON解码失败:", err)}fmt.Printf("解码后的商品: %+v\n", decodedProduct)
}
2 高级JSON处理技术
2.1 动态JSON处理
在实际开发中,我们经常需要处理结构不固定的JSON数据。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 vcase 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 vcase 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 vcase 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)// 输出修改后的JSONif modifiedJSON, err := processor.ToJSONIndent(); err == nil {fmt.Printf("\n修改后的JSON:\n%s\n", modifiedJSON)}
}
2.2 JSON流式处理
对于大型JSON数据,流式处理可以显著减少内存使用。
import ("io""strings"
)// JSON流处理器
type JSONStreamProcessor struct {decoder *json.Decoderencoder *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.Builderprocessor := 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响应结构
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 = truerb.response.Data = datareturn rb
}// 错误响应
func (rb *ResponseBuilder) Error(code, message, details string) *ResponseBuilder {rb.response.Success = falserb.response.Error = &APIError{Code: code,Message: message,Details: details,}return rb
}// 设置消息
func (rb *ResponseBuilder) Message(message string) *ResponseBuilder {rb.response.Message = messagereturn rb
}// 设置元数据
func (rb *ResponseBuilder) Meta(meta *Meta) *ResponseBuilder {rb.response.Meta = metareturn 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实现
// 用户管理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 Userif 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.nextIDapi.nextID++user.CreatedAt = time.Now()user.IsActive = true// 存储用户api.users[user.ID] = &userWriteCreated(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 := 1pageSize := 10if 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) * pageSizeend := start + pageSizeif 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.Pathparts := 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}// 提取用户IDpath := r.URL.Pathparts := 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 Userif err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())return}// 更新用户信息(保留原有的ID和创建时间)updateData.ID = existingUser.IDupdateData.CreatedAt = existingUser.CreatedAtnow := 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] = &updateDataWriteUpdated(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}// 提取用户IDpath := r.URL.Pathparts := 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 内容协商中间件
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 ContentTypeJSONcase strings.Contains(mediaType.Type, "application/xml"):return ContentTypeXMLcase strings.Contains(mediaType.Type, "text/plain"):return ContentTypeTextcase strings.Contains(mediaType.Type, "text/html"):return ContentTypeHTML}}return ContentTypeJSON // 默认返回JSON
}// 媒体类型结构
type MediaType struct {Type stringQuality float64Params map[string]string
}// 解析Accept头
func parseAcceptHeader(acceptHeader string) []MediaType {var mediaTypes []MediaTypeparts := 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.ResponseWriterWriter 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来综合运用本章学到的所有技术。
package mainimport ("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]*BooknextID 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.nextIDapi.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 := 1pageSize := 10if 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 []*Bookfor _, 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) * pageSizeend := start + pageSizeif 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 Bookif 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.nextIDapi.nextID++book.CreatedAt = time.Now()// 存储图书api.books[book.ID] = &bookWriteCreated(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 Bookif err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())return}// 保留原有信息updateData.ID = existingBook.IDupdateData.CreatedAt = existingBook.CreatedAtnow := 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] = &updateDataWriteUpdated(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)<-quitfmt.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测试示例
// 测试用例
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设计不仅能提升开发效率,还能为系统的扩展和维护提供便利。
