几天以来,我一直在为如何在 Go REST API 中处理 PATCH 请求而苦苦挣扎,直到我找到了一篇关于使用指针和omitempty
标签的文章,该文章我已经填充并且工作正常。很好,直到我意识到我仍然需要构建一个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
查询。我知道我可以使用反射来获取对象属性,但不知道如何获取它们的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,即使我最终可能只想修补一个属性,导致分配的内存比需要的多。 .知道如何避免这种情况吗?