从几天以来,我一直在努力研究如何在Go REST API中处理补丁请求,直到我找到了一个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
标记的问题是,对象可能缺少多个属性,因此我不能只准备一个带有硬编码字段和占位符的语句...我必须动态地构建它.
我的问题来了:how can I build such 100 query dynamically?在最好的情况下,我需要一些解决方案来标识集合属性,获取它们的SQL个字段名称(可能来自标记),然后我应该能够构建UPDATE
个查询.我知道我可以使用reflection来获取对象属性,但不知道如何获取它们的sql tag name,当然,如果可能的话,我希望避免在这里使用反射……或者我可以简单地判断每个属性,它不是nil
,但在现实生活中, struct 比这里提供的示例要大得多……
有人能帮我搬这个吗?是否已经有人需要解决相同/相似的情况?
SOLUTION:个
基于这里的答案,我能够想出这个抽象的解决方案.SQLPatches
方法从给定的 struct 构建SQLPatch
struct (因此没有特定的具体 struct ):
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点查.这里我看到的唯一问题是,我分配了两个片,在传递的 struct 中,字段的数量可能是10,即使我可能只想修补一个属性,最终导致分配的内存超过需要...Any idea how to avoid this?