英文:
strategy for REST API in go
问题
在我的数据库中,每一行对应一个结构体。
type datum struct{
Id *string `json:"task_id"`
Status *string `json:"status"`
AccountId *string `json:"account_id"`
.... // 还有很多其他字段,都是指针类型
}
在网页上,用户可以根据account_id
和status
等字段进行查询。服务器将返回满足查询条件的所有数据,并投影出Id
、account_id
和status
等字段。
目前,我编写了一个HTTP处理程序来完成以下任务:
从请求中提取查询条件作为一个datum
对象:
body, err := ioutil.ReadAll(r.Body)
condition := datum{}
err = json.Unmarshal(body, &condition)
使用部分填充的datum
对象来查询数据库,只有非空字段会转换为SELECT ... WHERE ..=..
语句。查询结果保存在query_result []datum
中。
将query_result
写入JSON对象以便回复:
reply := map[string]interface{}{
"reply": query_result,
}
data, err := json.Marshal(reply)
问题是,在回复中,许多字段的值为nil,但我仍然发送它们,这是浪费的。另一方面,我不想更改datum
结构体以包含omitempty
标签,因为在数据库中,值条目的所有字段都是非空的。
- 在这种情况下,我应该为回复定义一个新的结构体吗?有没有办法使用
datum
结构体来定义这个新的结构体,而不是硬编码一个? - 是否有更好的设计来实现这个查询功能?
英文:
In my database, each row corresponds to a struct
type datum struct{
Id *string `json:"task_id"`
Status *string `json:"status"`
AccountId *string `json:"account_id"`
.... // many more fields, all of pointer types
}
On the webpage, the user can query on several fields of datum (say account_id
and status
). The server will return all data that satisfy the query with a projection of the fields (say Id
, account_id
and status
).
Right now, I wrote a HTTP handler to
Extract the query as a datum object from the request:
body, err := ioutil.ReadAll(r.Body)
condition := datum{}
err = json.Unmarshal(body, &condition)
Use the partially filled datum object to query the database, only the non-nil fields translate to SELECT ... WHERE ..=..
. The query result is saved in query_result []datum
Write the query_result
into json object for reply:
reply := map[string]interface{}{
"reply": query_result,
}
data, err := json.Marshal(reply)
The problem is that in the reply many of the fields are nil, but I still send them, which is wasteful. On the other hand, I don't want to change the datum struct to include omitempty
tag because in the database a value entry has all fields non-nil.
- In this case, shall I define a new struct just for the reply? Is there a way to define this new struct using
datum
struct, instead of hard code one? - Is there a better design for this query feature?
答案1
得分: 1
你有几个选择,选择取决于在你的特定情况下什么更浪费/更昂贵:
- 只需在原始结构中使用指针+omitempty。
- 准备一个自定义的响应对象。但是你需要将原始结构中的值复制/转换到其导出版本中。
- 编写一个自定义的编组器,它将探索你的结构并创建一个导出就绪的变体,这样比#1更具动态性/自动性。
虽然#1不需要注释,而#2在上面由Gepser部分涵盖,但是下面是如何使用自定义编组器来解决这个问题(思路是重新组装输出,跳过空字段):
package main
import (
"fmt"
"encoding/json"
"reflect"
)
type datum struct {
Id *string `json:"task_id"`
Status *string `json:"status"`
AccountId *string `json:"account_id"`
}
type Response struct {
Reply []datum `json:"reply"`
}
func main() {
var query_result []datum
// 模拟带有空字段的查询结果
val_id_a := "id-a"
val_status := "status-b"
d1 := datum{
Id: &val_id_a,
Status: &val_status,
}
query_result = append(query_result, d1)
val_id_b := "id-b"
val_account_id := "account-id-b"
d2 := datum{
Id: &val_id_b,
AccountId: &val_account_id,
}
query_result = append(query_result, d2)
reply := &Response{
Reply: query_result,
}
data, err := json.Marshal(reply)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", string(data))
}
// MarshalJSON 是 Response 对象的自定义 JSON 编组器实现。
func (r *Response) MarshalJSON() ([]byte, error) {
a := struct {
Reply []map[string]interface{} `json:"reply"`
}{}
for _, v := range r.Reply {
a.Reply = append(a.Reply, converter(v))
}
return json.Marshal(a)
}
// converter 将结构转换为映射,跳过具有空值的字段。
func converter(in interface{}) map[string]interface{} {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
for i := 0; i < v.NumField(); i++ {
f := v.Type().Field(i)
tag := f.Tag.Get("json")
if tag != "" && !v.Field(i).IsNil() {
out[tag] = v.Field(i).Interface()
}
}
return out
}
希望对你有所帮助!
英文:
You have several options, with choice depending what is more wasteful/expensive in your particular case:
- Just use pointers+omitempty in the original struct.
- Prepare a custom response object. But you'll need to copy/convert the values from the original struct into its export version.
- Write a custom marshaller, that will be exploring your struct and creating an export-ready variant, this way being more dynamic/automatic that #1.
While #1 needs no comments, and #2 to some extend covered by Gepser above, here's how you can address this with a custom marshaller (the idea is to re-assemble your output skipping nil fields):
package main
import (
"fmt"
"encoding/json"
"reflect"
)
type datum struct {
Id *string `json:"task_id"`
Status *string `json:"status"`
AccountId *string `json:"account_id"`
}
type Response struct {
Reply []datum `json:"reply"`
}
func main() {
var query_result []datum
// mocking a query result with records with nil fields
val_id_a := "id-a"
val_status := "status-b"
d1 := datum{
Id: &val_id_a,
Status: &val_status,
}
query_result = append(query_result, d1)
val_id_b := "id-b"
val_account_id := "account-id-b"
d2 := datum{
Id: &val_id_b,
AccountId: &val_account_id,
}
query_result = append(query_result, d2)
reply := &Response{
Reply: query_result,
}
data, err := json.Marshal(reply)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", string(data))
}
// MarshalJSON is a custom JSON marshaller implementation for Response object.
func (r *Response) MarshalJSON() ([]byte, error) {
a := struct {
Reply []map[string]interface{} `json:"reply"`
}{}
for _, v := range r.Reply {
a.Reply = append(a.Reply, converter(v))
}
return json.Marshal(a)
}
// converter converts a struct into a map, skipping fields with nil values.
func converter(in interface{}) map[string]interface{} {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
for i := 0; i < v.NumField(); i++ {
f := v.Type().Field(i)
tag := f.Tag.Get("json")
if tag != "" && !v.Field(i).IsNil() {
out[tag] = v.Field(i).Interface()
}
}
return out
}
答案2
得分: 0
我建议的方法(也是我使用的方法)是使用带有omitempty
标签的new struct
,例如:
type datumResponse struct{
Id *string `json:"task_id,omitempty"`
Status *string `json:"status,omitempty"`
AccountId *string `json:"account_id,omitempty"`
.... // 更多字段
}
如果没有子结构或者你不写一个结构数组,那么就没有办法使用旧结构的字段来编写新的struct
。
英文:
The approach I suggest (is the one I use) is the new struct
with omitempty
tag, for example:
type datumResponse struct{
Id *string `json:"task_id,omitempty"`
Status *string `json:"status,omitempty"`
AccountId *string `json:"account_id,omitempty"`
.... // many more fields
}
and there is no option to write your new struct
using the fields of the old one if there is not substructs or you don't write an array of structs.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论