【发布时间】:2016-07-05 14:48:40
【问题描述】:
几天以来,我一直在努力解决如何在 Go REST API 中处理 PATCH 请求,直到我找到了一个 article about using pointers and omitempty tag,我已经填充并且工作正常。很好,直到我意识到我仍然需要构建一个 UPDATE SQL 查询。
我的struct 看起来像这样:
type Resource struct {
Name *string `json:"name,omitempty" sql:"resource_id"`
Description *string `json:"description,omitempty" sql:"description"`
}
我期待一个包含此类请求正文的 PATCH /resources/{resource-id} 请求:
{"description":"Some new description"}
在我的处理程序中,我将以这种方式构建 Resource 对象(忽略导入,忽略错误处理):
var resource Resource
resourceID, _ := mux.Vars(r)["resource-id"]
d := json.NewDecoder(r.Body)
d.Decode(&resource)
// at this point our resource object should only contain
// the Description field with the value from JSON in request body
现在,对于正常的UPDATE(PUT 请求)我会这样做(简化):
stmt, _ := db.Prepare(`UPDATE resources SET description = ?, name = ? WHERE resource_id = ?`)
res, _ := stmt.Exec(resource.Description, resource.Name, resourceID)
PATCH 和 omitempty 标记的问题是对象可能缺少多个属性,因此我不能只准备带有硬编码字段和占位符的语句...我必须动态构建它。
我的问题来了:我怎样才能动态构建这样的UPDATE 查询?在最好的情况下,我需要一些解决方案来识别设置的属性,获取它们的SQL 字段名称(可能来自标签),然后我应该能够构建UPDATE 查询。我知道我可以使用 reflection 来获取对象属性,但不知道如何获取它们的 sql 标记名称,当然我想尽可能避免在此处使用反射...或者我可以简单地检查不是nil的每个属性,但在现实生活中,结构比此处提供的示例大得多...
有人可以帮我解决这个问题吗?是否有人已经必须解决相同/相似的情况?
解决方案:
根据此处的答案,我能够提出这个抽象的解决方案。 SQLPatches 方法从给定结构构建SQLPatch 结构(因此没有具体的结构):
import (
"fmt"
"encoding/json"
"reflect"
"strings"
)
const tagname = "sql"
type SQLPatch struct {
Fields []string
Args []interface{}
}
func SQLPatches(resource interface{}) SQLPatch {
var sqlPatch SQLPatch
rType := reflect.TypeOf(resource)
rVal := reflect.ValueOf(resource)
n := rType.NumField()
sqlPatch.Fields = make([]string, 0, n)
sqlPatch.Args = make([]interface{}, 0, n)
for i := 0; i < n; i++ {
fType := rType.Field(i)
fVal := rVal.Field(i)
tag := fType.Tag.Get(tagname)
// skip nil properties (not going to be patched), skip unexported fields, skip fields to be skipped for SQL
if fVal.IsNil() || fType.PkgPath != "" || tag == "-" {
continue
}
// if no tag is set, use the field name
if tag == "" {
tag = fType.Name
}
// and make the tag lowercase in the end
tag = strings.ToLower(tag)
sqlPatch.Fields = append(sqlPatch.Fields, tag+" = ?")
var val reflect.Value
if fVal.Kind() == reflect.Ptr {
val = fVal.Elem()
} else {
val = fVal
}
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
sqlPatch.Args = append(sqlPatch.Args, val.Int())
case reflect.String:
sqlPatch.Args = append(sqlPatch.Args, val.String())
case reflect.Bool:
if val.Bool() {
sqlPatch.Args = append(sqlPatch.Args, 1)
} else {
sqlPatch.Args = append(sqlPatch.Args, 0)
}
}
}
return sqlPatch
}
那我可以这么简单地称呼它:
type Resource struct {
Description *string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
}
func main() {
var r Resource
json.Unmarshal([]byte(`{"description": "new description"}`), &r)
sqlPatch := SQLPatches(r)
data, _ := json.Marshal(sqlPatch)
fmt.Printf("%s\n", data)
}
您可以通过Go Playground查看。我在这里看到的唯一问题是,我为两个切片分配了传递的结构中的字段数量,可能是 10,即使我最终可能只想修补一个属性,导致分配的内存比需要的多。 . 知道如何避免这种情况吗?
【问题讨论】:
-
那么,现在是 2021 年了。有没有更好的解决方案?
-
@GHopper 您好,感谢您的评论。我在这里添加了我自己的答案,你也可以在那里找到你的问题的答案:-)
标签: sql rest go http-patch