strategy for REST API in go

时间:2016-12-09 12:57:18

标签: database rest go

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?

2 个答案:

答案 0 :(得分:1)

您有多种选择,可根据您的具体情况选择更加浪费/昂贵的选项:

  1. 只需在原始结构中使用指针+ omitempty。
  2. 准备自定义响应对象。但是您需要将原始结构中的值复制/转换为其导出版本。
  3. 编写一个自定义编组程序,它将探索您的结构并创建一个可导出的变体,这种方式更加动态/自动化#1。
  4. 虽然#1不需要评论,而且#2在上面的Gepser所涵盖的范围内,但是你可以通过自定义编组器解决这个问题(想法是重新组合你的输出跳过nil字段):

    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
    }
    

答案 1 :(得分:0)

我建议的方法(就是我使用的方法)是带有new struct标记的omitempty,例如:

type datumResponse struct{
    Id *string `json:"task_id,omitempty"`
    Status *string `json:"status,omitempty"`
    AccountId *string `json:"account_id,omitempty"`
    .... // many more fields
}

如果没有子结构或者你没有编写结构数组,则没有选择使用旧字段的字段编写新的struct