Go中jsonpatch.Patch反序列化对象失败问题及解决方法
JSON Patch解码错误:无法将对象反序列化为jsonpatch.Patch类型
问题场景
使用Go 1.21.1结合Gin框架编写Issue资源的HTTP PATCH接口,引入github.com/evanphx/json-patch库处理JSON Patch操作。执行patch, err := jsonpatch.DecodePatch(patchJSON)时触发错误:
json: cannot unmarshal object into Go value of type jsonpatch.Patch
发送请求的curl命令:
curl -d '{"op": "replace", "path": "/summarybrief", "value": "Second Test Issue"}' -X PATCH http://localhost:8080/issue/22 -H "Content-Type: application/json"
完整接口代码:
import ( "database/sql" "encoding/json" "fmt" "io" "net/http" jsonpatch "github.com/evanphx/json-patch" "github.com/gin-gonic/gin" "github.com/jasonalansmith/maelstrom-platform-api/database" ) type Issue struct { SysId uint `json:"sysid,omitempty"` Identifier string `json:"identifier,omitempty"` SummaryBrief string `json:"summarybrief,omitempty"` SummaryLong string `json:"summarylong,omitempty"` } func patchIssue(ctx *gin.Context) { id := ctx.Param("sysid") iss := Issue{} sqls := "SELECT * FROM issue WHERE sysid = $1" res := database.Db.QueryRow(sqls, id) err := res.Scan(&iss.SysId, &iss.Identifier, &iss.SummaryBrief, &iss.SummaryLong) if err == sql.ErrNoRows { ctx.AbortWithStatusJSON(400, "Issue does not exist.") return } issueBytes, err := json.Marshal(iss) if err != nil { fmt.Println("Error creating patch json ", err.Error()) return } patchJSON, err := io.ReadAll(ctx.Request.Body) fmt.Println(string(patchJSON)) if err != nil { fmt.Println(err) } patch, err := jsonpatch.DecodePatch(patchJSON) if err != nil { fmt.Println("Error decoding patch json ", err.Error()) return } patchedIssue, err := patch.Apply(issueBytes) if err != nil { fmt.Println("Error applying patch json ", err.Error()) return } fmt.Println(patchedIssue) }
问题原因
github.com/evanphx/json-patch库严格遵循RFC 6902 JSON Patch规范,该规范要求请求体必须是包含一个或多个操作对象的数组,即使只有单个操作也需要用数组包裹。而当前curl发送的是单个JSON对象,不符合库的输入要求,因此触发反序列化错误。
解决方案
方案1:修改请求格式(推荐)
将curl请求中的单个操作对象包裹成数组:
curl -d '[{"op": "replace", "path": "/summarybrief", "value": "Second Test Issue"}]' -X PATCH http://localhost:8080/issue/22 -H "Content-Type: application/json"
方案2:代码兼容单个对象输入(可选)
如果需要兼容客户端发送的单个操作对象,可以在代码中先判断输入格式,将单个对象转换为数组后再解码:
// 在读取patchJSON之后添加以下处理逻辑 var raw interface{} if err := json.Unmarshal(patchJSON, &raw); err != nil { fmt.Println("Invalid JSON:", err.Error()) ctx.AbortWithStatusJSON(http.StatusBadRequest, "Invalid patch format") return } var patchArray []byte switch v := raw.(type) { case map[string]interface{}: // 单个操作对象,包装为数组 arr := []interface{}{v} var err error patchArray, err = json.Marshal(arr) if err != nil { fmt.Println("Failed to wrap patch:", err.Error()) ctx.AbortWithStatusJSON(http.StatusInternalServerError, "Failed to process patch") return } case []interface{}: // 已经是数组格式,直接序列化 var err error patchArray, err = json.Marshal(v) if err != nil { fmt.Println("Failed to marshal patch array:", err.Error()) ctx.AbortWithStatusJSON(http.StatusInternalServerError, "Failed to process patch") return } default: ctx.AbortWithStatusJSON(http.StatusBadRequest, "Invalid patch format: must be object or array") return } // 使用处理后的数组数据解码patch patch, err := jsonpatch.DecodePatch(patchArray)
内容的提问来源于stack exchange,提问作者Jason Alan Smith
相关产品推荐
相关产品推荐

