Go语言中如何更简洁实现前端JSON到API结构体的归一化?
优化Go API的JSON数据归一化处理
问题背景
作为Go语言新手,当前处理前端传入API的JSON数据时,先将载荷转存为map[string]interface{},再手动做类型转换和归一化,最后转成model.Expense结构体。希望找到更简洁、更符合Go编程习惯的实现方式。
原model.Expense结构体:
type Expense struct { Id primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"` Name string `json:"name"` Frequency int `json:"frequency"` StartDate *time.Time `json:"startDate"` EndDate *time.Time `json:"endDate,omitempty"` Cost primitive.Decimal128 `json:"cost"` Paid []string `json:"paid,omitempty"` }
原控制器核心逻辑(手动转map处理):
func InsertOneExpense(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Allow-Control-Allow-Methods", "POST") var expense map[string]interface{} json.NewDecoder(r.Body).Decode(&expense) var expenseName string if name, ok := expense["name"]; ok { expenseName = fmt.Sprintf("%v", name) } else { json.NewEncoder(w).Encode("missing required name") } var expenseFrequency int if frequency, ok := expense["frequency"]; ok { expenseFrequency = int(frequency.(float64)) } else { expenseFrequency = 1 } // 处理startDate归一化 var expenseStartDate *time.Time if startDate, ok := expense["startDate"]; ok { startDateString := fmt.Sprintf("%v", startDate) startDateParsed, err := time.Parse("2006-01-02 15:04:05", startDateString) if err != nil { log.Fatal(err) } expenseStartDate = &startDateParsed } else { json.NewEncoder(w).Encode("missing required startDate") } // 处理endDate归一化 var expenseEndDate *time.Time if endDate, ok := expense["endDate"]; ok { endDateString := fmt.Sprintf("%v", endDate) endDateParsed, err := time.Parse("2006-01-02 15:04:05", endDateString) if err != nil { log.Fatal(err) } expenseEndDate = &endDateParsed } else { expenseEndDate = nil } // 处理cost归一化 var expenseCost primitive.Decimal128 if cost, ok := expense["cost"]; ok { costString := fmt.Sprintf("%v", cost) costPrimitive, err := primitive.ParseDecimal128(costString) if err != nil { log.Fatal(err) } expenseCost = costPrimitive } else { json.NewEncoder(w).Encode("missing required cost") return } normalizedExpense := model.Expense{ Name: expenseName, Frequency: expenseFrequency, StartDate: expenseStartDate, EndDate: expenseEndDate, Cost: expenseCost, } // 对结构体变量执行后续操作... }
优化方案
1. 直接解码到结构体,自定义特殊字段解析
无需转存为map,直接将JSON解码到Expense结构体,通过实现json.Unmarshaler接口处理特殊类型(时间、Decimal128)的自动转换,避免手动类型断言。
修改后的Expense结构体及自定义解析方法:
package model import ( "encoding/json" "fmt" "time" "go.mongodb.org/mongo-driver/bson/primitive" ) type Expense struct { Id primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"` Name string `json:"name"` Frequency int `json:"frequency"` StartDate *time.Time `json:"startDate"` EndDate *time.Time `json:"endDate,omitempty"` Cost primitive.Decimal128 `json:"cost"` Paid []string `json:"paid,omitempty"` } // 自定义StartDate的JSON解析,兼容前端传入的字符串格式 func (t *time.Time) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } parsedTime, err := time.Parse("2006-01-02 15:04:05", s) if err != nil { return err } *t = parsedTime return nil } // 自定义Cost的JSON解析,将数字/字符串转为Decimal128 func (d *primitive.Decimal128) UnmarshalJSON(data []byte) error { var s interface{} if err := json.Unmarshal(data, &s); err != nil { return err } var str string switch v := s.(type) { case string: str = v case float64: str = fmt.Sprintf("%f", v) default: return fmt.Errorf("invalid cost type") } dec, err := primitive.ParseDecimal128(str) if err != nil { return err } *d = dec return nil }
2. 简化必填字段验证与默认值处理
解码后统一做必填字段校验,默认值直接在结构体初始化时设置,无需逐个字段判断。
3. 错误处理优化
替换log.Fatal为HTTP错误响应,避免进程意外退出,同时统一返回JSON格式错误信息;修正原代码中CORS头的拼写错误(Allow-Control-Allow-Methods → Access-Control-Allow-Methods)。
优化后的控制器代码
func InsertOneExpense(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Methods", "POST") var expense model.Expense // 设置Frequency默认值 expense.Frequency = 1 // 直接解码到结构体 if err := json.NewDecoder(r.Body).Decode(&expense); err != nil { http.Error(w, jsonError("invalid request body: "+err.Error()), http.StatusBadRequest) return } // 必填字段校验 if expense.Name == "" { http.Error(w, jsonError("missing required field: name"), http.StatusBadRequest) return } if expense.StartDate == nil { http.Error(w, jsonError("missing required field: startDate"), http.StatusBadRequest) return } // 检查Cost是否为零值(primitive.Decimal128零值表示未设置) if expense.Cost.IsZero() { http.Error(w, jsonError("missing required field: cost"), http.StatusBadRequest) return } // 后续操作(如存入数据库)... // example: result, err := collection.InsertOne(context.TODO(), expense) json.NewEncoder(w).Encode(map[string]interface{}{ "status": "success", "data": expense, }) } // 辅助函数:返回JSON格式错误信息 func jsonError(msg string) string { b, _ := json.Marshal(map[string]string{"error": msg}) return string(b) }
内容的提问来源于stack exchange,提问作者sivs
相关产品推荐
相关产品推荐

