数日以来、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
andタグの問題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で確認できます。ここで私が目にする唯一の問題は、渡された構造体のフィールドの量で両方のスライスを割り当てることです。これは、最終的に 1 つのプロパティにパッチを適用したいだけで、必要以上のメモリを割り当てることになる場合があります.. .これを回避する方法はありますか?