Xóa các trường khỏi struct hoặc ẩn chúng trong JSON Feedback


181

Tôi đã tạo một API trong Go, khi được gọi, thực hiện một truy vấn, tạo một thể hiện của một cấu trúc và sau đó mã hóa cấu trúc đó dưới dạng JSON trước khi gửi lại cho người gọi. Bây giờ tôi muốn cho phép người gọi có thể chọn các trường cụ thể mà họ muốn trả về bằng cách chuyển vào tham số GET "trường".

Điều này có nghĩa là tùy thuộc vào (các) giá trị trường, cấu trúc của tôi sẽ thay đổi. Có cách nào để loại bỏ các trường từ một cấu trúc? Hoặc ít nhất là ẩn chúng trong phản hồi JSON một cách linh hoạt? (Lưu ý: Đôi khi tôi có các giá trị trống nên thẻ omitEmpty JSON sẽ không hoạt động ở đây) Nếu cả hai điều này đều không thể, có gợi ý nào về cách tốt hơn để xử lý việc này không? Cảm ơn trước.

Một phiên bản nhỏ hơn của các cấu trúc tôi đang sử dụng ở bên dưới:

type SearchResult struct {
    Date        string      `json:"date"`
    IdCompany   int         `json:"idCompany"`
    Company     string      `json:"company"`
    IdIndustry  interface{} `json:"idIndustry"`
    Industry    string      `json:"industry"`
    IdContinent interface{} `json:"idContinent"`
    Continent   string      `json:"continent"`
    IdCountry   interface{} `json:"idCountry"`
    Country     string      `json:"country"`
    IdState     interface{} `json:"idState"`
    State       string      `json:"state"`
    IdCity      interface{} `json:"idCity"`
    City        string      `json:"city"`
} //SearchResult

type SearchResults struct {
    NumberResults int            `json:"numberResults"`
    Results       []SearchResult `json:"results"`
} //type SearchResults

Sau đó tôi mã hóa và xuất ra phản hồi như vậy:

err := json.NewEncoder(c.ResponseWriter).Encode(&msg)

7
@Jacob, theo câu trả lời cập nhật của PuerkitoBio, tôi nghĩ bạn đã đọc sai câu hỏi. (Hiện tại) được chấp nhận có thể không phải là "câu trả lời chính xác" cho câu hỏi của bạn , nhưng là câu hỏi được hỏi ở đây! Câu trả lời được bình chọn cao nhất (hiện tại) có thể trả lời câu hỏi của bạn nhưng hoàn toàn không thể áp dụng cho câu hỏi này!
Dave C

Câu trả lời:


275

EDIT: Tôi nhận thấy một vài downvote và có cái nhìn khác về Q & A này. Hầu hết mọi người dường như bỏ lỡ rằng OP yêu cầu các trường được chọn động dựa trên danh sách các trường do người gọi cung cấp. Bạn không thể làm điều này với thẻ struct json được xác định tĩnh.

Nếu điều bạn muốn là luôn bỏ qua một trường để mã hóa json, thì dĩ nhiên sử dụng json:"-"để bỏ qua trường đó (cũng lưu ý rằng điều này là không bắt buộc nếu trường của bạn không được báo cáo - những trường đó luôn bị bộ mã hóa json bỏ qua). Nhưng đó không phải là câu hỏi của OP.

Để trích dẫn nhận xét về json:"-"câu trả lời:

[ json:"-"Câu trả lời] này là câu trả lời mà hầu hết mọi người kết thúc ở đây từ tìm kiếm đều muốn, nhưng nó không phải là câu trả lời cho câu hỏi.


Tôi sẽ sử dụng giao diện map [chuỗi] {} thay vì struct trong trường hợp này. Bạn có thể dễ dàng xóa các trường bằng cách gọidelete hợp trên bản đồ cho các trường cần xóa.

Đó là, nếu bạn không thể chỉ truy vấn cho các trường được yêu cầu ở vị trí đầu tiên.


4
rất có thể bạn không muốn vứt bỏ hoàn toàn định nghĩa kiểu của mình. Điều đó sẽ gây khó chịu cho dòng này, giống như khi bạn muốn viết các phương thức khác trên loại này truy cập vào các trường đó. Sử dụng một trung gian map[string]interface{}có ý nghĩa, nhưng nó không yêu cầu bạn vứt bỏ định nghĩa loại của bạn.
jorelli

1
Câu trả lời khác là câu trả lời thực tế cho câu hỏi này.
Jacob

1
Một nhược điểm có thể có của việc xóa là đôi khi bạn có thể muốn hỗ trợ nhiều chế độ xem json cho cấu trúc (bản đồ) của mình. Ví dụ, chế độ xem json cho máy khách không có trường nhạy cảm và chế độ xem json cho cơ sở dữ liệu VỚI trường nhạy cảm. May mắn là vẫn có thể sử dụng struct - chỉ cần nhìn vào câu trả lời của tôi.
Adam Kurkiewicz 8/07/2015

Điều này làm việc cho tôi vì tôi chỉ cần một cụ thể Idnhưng, không muốn trả lại toàn bộ cấu trúc json. Cảm ơn vì điều đó!
Louie Miranda

155

sử dụng `json:" - "`

// Field is ignored by this package.
Field int `json:"-"`

// Field appears in JSON as key "myName".
Field int `json:"myName"`

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

doc: http://golang.org/pkg/encoding/json/#Marshal


14
Tôi không đồng ý @Jacob vì OP cho biết họ muốn kiểm soát linh hoạt các trường đầu ra dựa trên các mục nhập chuỗi truy vấn vào API. Ví dụ: nếu người gọi API chỉ yêu cầu Công nghiệp và Quốc gia, thì bạn sẽ cần xóa phần còn lại. Đây là lý do tại sao câu trả lời "đánh dấu" được đánh dấu là một câu trả lời cho câu hỏi này. Câu trả lời được đánh giá cao này là để đánh dấu các lĩnh vực rõ ràng không bao giờ có sẵn cho bất kỳ-xây dựng-json-marshaler - EVER. nếu bạn muốn nó một cách linh hoạt, câu trả lời được đánh dấu là câu trả lời.
eduncan911

11
Đây là câu trả lời mà hầu hết mọi người kết thúc ở đây từ tìm kiếm đều muốn, nhưng nó không phải là câu trả lời cho câu hỏi.
Filip Haglund 16/1/2015

5
Như đã nêu, OP đã yêu cầu một phương thức tự động hình thành DTO.
codepushr

53

Một cách khác để làm điều này là có một cấu trúc con trỏ với ,omitemptythẻ. Nếu con trỏ là con số không , thì các trường sẽ không bị Marshall.

Phương pháp này sẽ không yêu cầu phản ánh bổ sung hoặc sử dụng bản đồ không hiệu quả.

Ví dụ tương tự như jorelli sử dụng phương pháp này: http://play.golang.org/p/JJNa0m2_nw


3
+1 Hoàn toàn đồng ý. Tôi luôn sử dụng quy tắc / mẹo này với các nguyên soái tích hợp (và thậm chí còn xây dựng trình đọc / ghi CSV dựa trên quy tắc này! - Tôi có thể mở nguồn ngay khi có gói csv khác). OP sau đó có thể đơn giản là không đặt giá trị * Quốc gia thành 0 và nó sẽ bị bỏ qua. Và thật tuyệt vời khi bạn đã cung cấp một play.golang tuyệt vời.
eduncan911

2
Tất nhiên phương pháp đó đòi hỏi sự phản chiếu, việc sắp xếp thứ tự json-to-struct của stdlib luôn sử dụng sự phản chiếu (thực ra nó luôn sử dụng thời gian phản xạ, bản đồ hoặc cấu trúc hoặc bất cứ điều gì).
mna

Có, nhưng nó không yêu cầu phản ánh bổ sung bằng các giao diện, mà một số câu trả lời khác khuyến nghị.
Druska 30/03/2015

14

Bạn có thể sử dụng reflectgói để chọn các trường mà bạn muốn bằng cách phản ánh trên các thẻ trường và chọn các jsongiá trị thẻ. Xác định một phương thức trên quả tìm kiếm của bạn gõ mà chọn lĩnh vực mà bạn muốn và lợi nhuận họ như là một map[string]interface{}, và sau đó marshal rằng thay vì searchresults struct chính nó. Đây là một ví dụ về cách bạn có thể định nghĩa phương thức đó:

func fieldSet(fields ...string) map[string]bool {
    set := make(map[string]bool, len(fields))
    for _, s := range fields {
        set[s] = true
    }
    return set
}

func (s *SearchResult) SelectFields(fields ...string) map[string]interface{} {
    fs := fieldSet(fields...)
    rt, rv := reflect.TypeOf(*s), reflect.ValueOf(*s)
    out := make(map[string]interface{}, rt.NumField())
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        jsonKey := field.Tag.Get("json")
        if fs[jsonKey] {
            out[jsonKey] = rv.Field(i).Interface()
        }
    }
    return out
}

và đây là một giải pháp có thể chạy được cho thấy cách bạn sẽ gọi phương thức này và sắp xếp lựa chọn của bạn: http://play.golang.org/p/1K9xjQRnO8


Nghĩ về nó, bạn có thể khái quát một cách hợp lý mô hình selectfields cho bất kỳ loại và bất kỳ khóa thẻ nào; không có gì về điều này đặc trưng cho định nghĩa SearchResult hoặc khóa json.
jorelli

Tôi đang cố gắng tránh xa sự phản chiếu nhưng điều này giúp tiết kiệm thông tin loại khá độc đáo ... Thật tuyệt khi có mã ghi lại các cấu trúc của bạn trông tốt hơn một loạt các thẻ if / khác trong phương thức xác thực () (nếu bạn thậm chí có một)
Aktau

7

Tôi vừa xuất bản cảnh sát trưởng , trong đó chuyển đổi các cấu trúc thành bản đồ dựa trên các thẻ được chú thích trên các trường cấu trúc. Sau đó, bạn có thể sắp xếp (JSON hoặc những người khác) bản đồ được tạo. Có thể nó không cho phép bạn chỉ tuần tự hóa tập hợp các trường mà người gọi yêu cầu, nhưng tôi tưởng tượng rằng việc sử dụng một nhóm các nhóm sẽ cho phép bạn bao quát hầu hết các trường hợp. Sử dụng các nhóm thay vì các trường trực tiếp rất có thể cũng sẽ tăng khả năng bộ nhớ cache.

Thí dụ:

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/hashicorp/go-version"
    "github.com/liip/sheriff"
)

type User struct {
    Username string   `json:"username" groups:"api"`
    Email    string   `json:"email" groups:"personal"`
    Name     string   `json:"name" groups:"api"`
    Roles    []string `json:"roles" groups:"api" since:"2"`
}

func main() {
    user := User{
        Username: "alice",
        Email:    "alice@example.org",
        Name:     "Alice",
        Roles:    []string{"user", "admin"},
    }

    v2, err := version.NewVersion("2.0.0")
    if err != nil {
        log.Panic(err)
    }

    o := &sheriff.Options{
        Groups:     []string{"api"},
        ApiVersion: v2,
    }

    data, err := sheriff.Marshal(o, user)
    if err != nil {
        log.Panic(err)
    }

    output, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        log.Panic(err)
    }
    fmt.Printf("%s", output)
}

7

Lấy ba thành phần:

  1. Các reflectgói để lặp trên tất cả các lĩnh vực của một struct.

  2. Một iftuyên bố để chọn các lĩnh vực bạn muốn Marshal

  3. Các encoding/jsongói cho Marshalcác lĩnh vực của bạn thích.

Sự chuẩn bị:

  1. Trộn chúng theo tỷ lệ tốt. Sử dụng reflect.TypeOf(your_struct).Field(i).Name()để có được một tên của ilĩnh vực thứ your_struct.

  2. Sử dụng reflect.ValueOf(your_struct).Field(i)để có được một Valueđại diện loại của một itrường thứ your_struct.

  3. Sử dụng fieldValue.Interface()để truy xuất giá trị thực (được chiếu lên để gõ giao diện {}) fieldValuecủa loại Value(lưu ý sử dụng dấu ngoặc - phương thức Interface () tạo rainterface{}

Nếu bạn may mắn không đốt cháy bất kỳ bóng bán dẫn hoặc bộ ngắt mạch nào trong quy trình, bạn sẽ nhận được một cái gì đó như thế này:

func MarshalOnlyFields(structa interface{},
    includeFields map[string]bool) (jsona []byte, status error) {
    value := reflect.ValueOf(structa)
    typa := reflect.TypeOf(structa)
    size := value.NumField()
    jsona = append(jsona, '{')
    for i := 0; i < size; i++ {
        structValue := value.Field(i)
        var fieldName string = typa.Field(i).Name
        if marshalledField, marshalStatus := json.Marshal((structValue).Interface()); marshalStatus != nil {
            return []byte{}, marshalStatus
        } else {
            if includeFields[fieldName] {
                jsona = append(jsona, '"')
                jsona = append(jsona, []byte(fieldName)...)
                jsona = append(jsona, '"')
                jsona = append(jsona, ':')
                jsona = append(jsona, (marshalledField)...)
                if i+1 != len(includeFields) {
                    jsona = append(jsona, ',')
                }
            }
        }
    }
    jsona = append(jsona, '}')
    return
}

Phục vụ:

phục vụ với một cấu trúc tùy ý và một map[string]booltrường bạn muốn đưa vào, ví dụ

type magic struct {
    Magic1 int
    Magic2 string
    Magic3 [2]int
}

func main() {
    var magic = magic{0, "tusia", [2]int{0, 1}}
    if json, status := MarshalOnlyFields(magic, map[string]bool{"Magic1": true}); status != nil {
        println("error")
    } else {
        fmt.Println(string(json))
    }

}

Chúc ngon miệng!


Cảnh báo! Nếu bao gồm các sản phẩm của bạn có chứa các tên trường không khớp với các trường thực tế, bạn sẽ nhận được một json không hợp lệ. Bạn đã được cảnh báo.
Adam Kurkiewicz 8/07/2015

5

Bạn có thể sử dụng thuộc tính gắn thẻ "omitifempty" hoặc tạo các con trỏ trường tùy chọn và bỏ qua những cái bạn muốn bỏ qua chưa được khởi tạo.


Đây là câu trả lời đúng nhất cho câu hỏi OP và trường hợp sử dụng.
dùng1943442

2
@ user1943442, không phải vậy; OP đề cập rõ ràng lý do tại sao "thiếu sót" là không thể áp dụng.
Dave C

2

Tôi cũng phải đối mặt với vấn đề này, lúc đầu tôi chỉ muốn chuyên môn hóa các phản hồi trong trình xử lý http của mình. Cách tiếp cận đầu tiên của tôi là tạo ra một gói sao chép thông tin của một cấu trúc sang một cấu trúc khác và sau đó sắp xếp cấu trúc thứ hai đó. Tôi đã thực hiện gói đó bằng cách sử dụng sự phản chiếu, vì vậy, không bao giờ thích cách tiếp cận đó và tôi cũng không năng động.

Vì vậy, tôi quyết định sửa đổi gói mã hóa / json để làm điều này. Các chức năng Marshal, MarshalIndent(Encoder) Encodecũng nhận được một

type F map[string]F

Tôi muốn mô phỏng JSON của các trường cần thiết cho nguyên soái, vì vậy nó chỉ sắp xếp các trường trong bản đồ.

https://github.com/JuanTorr/jsont

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/JuanTorr/jsont"
)

type SearchResult struct {
    Date        string      `json:"date"`
    IdCompany   int         `json:"idCompany"`
    Company     string      `json:"company"`
    IdIndustry  interface{} `json:"idIndustry"`
    Industry    string      `json:"industry"`
    IdContinent interface{} `json:"idContinent"`
    Continent   string      `json:"continent"`
    IdCountry   interface{} `json:"idCountry"`
    Country     string      `json:"country"`
    IdState     interface{} `json:"idState"`
    State       string      `json:"state"`
    IdCity      interface{} `json:"idCity"`
    City        string      `json:"city"`
} //SearchResult

type SearchResults struct {
    NumberResults int            `json:"numberResults"`
    Results       []SearchResult `json:"results"`
} //type SearchResults
func main() {
    msg := SearchResults{
        NumberResults: 2,
        Results: []SearchResult{
            {
                Date:        "12-12-12",
                IdCompany:   1,
                Company:     "alfa",
                IdIndustry:  1,
                Industry:    "IT",
                IdContinent: 1,
                Continent:   "america",
                IdCountry:   1,
                Country:     "México",
                IdState:     1,
                State:       "CDMX",
                IdCity:      1,
                City:        "Atz",
            },
            {
                Date:        "12-12-12",
                IdCompany:   2,
                Company:     "beta",
                IdIndustry:  1,
                Industry:    "IT",
                IdContinent: 1,
                Continent:   "america",
                IdCountry:   2,
                Country:     "USA",
                IdState:     2,
                State:       "TX",
                IdCity:      2,
                City:        "XYZ",
            },
        },
    }
    fmt.Println(msg)
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        //{"numberResults":2,"results":[{"date":"12-12-12","idCompany":1,"idIndustry":1,"country":"México"},{"date":"12-12-12","idCompany":2,"idIndustry":1,"country":"USA"}]}
        err := jsont.NewEncoder(w).Encode(msg, jsont.F{
            "numberResults": nil,
            "results": jsont.F{
                "date":       nil,
                "idCompany":  nil,
                "idIndustry": nil,
                "country":    nil,
            },
        })
        if err != nil {
            log.Fatal(err)
        }
    })

    http.ListenAndServe(":3009", nil)
}

Tôi chưa thử, nhưng nó trông rất tuyệt. Sẽ tốt hơn nữa nếu giao diện Marshaler cũng được hỗ trợ.
huggie

1

Câu hỏi bây giờ hơi cũ, nhưng tôi đã gặp một vấn đề tương tự cách đây ít lâu, và vì tôi thấy không có cách nào dễ dàng để làm điều này, tôi đã xây dựng một thư viện đáp ứng mục đích này. Nó cho phép dễ dàng tạo ra mộtmap[string]interface{} từ một cấu trúc tĩnh.

https://github.com/tuvistavie/structomap


Bây giờ bạn có thể dễ dàng làm điều đó bằng cách sử dụng một đoạn mã từ công thức của tôi.
Adam Kurkiewicz

Đoạn mã là một tập hợp con của thư viện, nhưng một vấn đề lớn ở đây về việc trả lại một []bytelà nó không được tái sử dụng nhiều: chẳng hạn, cách dễ dàng để thêm một trường sau đó. Vì vậy, tôi sẽ đề nghị tạo một map[string]interface{}và để phần tuần tự hóa JSON vào thư viện chuẩn.
Daniel Perez

1

Tôi không có cùng một vấn đề nhưng tương tự. Mã bên dưới cũng giải quyết vấn đề của bạn, tất nhiên nếu bạn không quan tâm đến vấn đề hiệu suất. Trước khi thực hiện loại giải pháp đó cho hệ thống của bạn, tôi khuyên bạn nên thiết kế lại cấu trúc của mình nếu có thể. Gửi phản ứng cấu trúc biến là kỹ thuật quá mức. Tôi tin rằng một cấu trúc phản hồi thể hiện một hợp đồng giữa một yêu cầu và tài nguyên và nó không nên là các yêu cầu phụ thuộc. (Tôi có thể làm cho các trường không mong muốn trở thành null, tôi làm vậy). Trong một số trường hợp, chúng tôi phải thực hiện thiết kế này, nếu bạn tin rằng bạn đang ở trong trường hợp đó thì đây là liên kết chơi và mã tôi sử dụng.

type User2 struct {
    ID       int    `groups:"id" json:"id,omitempty"`
    Username string `groups:"username" json:"username,omitempty"`
    Nickname string `groups:"nickname" json:"nickname,omitempty"`
}

type User struct {
    ID       int    `groups:"private,public" json:"id,omitempty"`
    Username string `groups:"private" json:"username,omitempty"`
    Nickname string `groups:"public" json:"nickname,omitempty"`
}

var (
    tagName = "groups"
)

//OmitFields sets fields nil by checking their tag group value and access control tags(acTags)
func OmitFields(obj interface{}, acTags []string) {
    //nilV := reflect.Value{}
    sv := reflect.ValueOf(obj).Elem()
    st := sv.Type()
    if sv.Kind() == reflect.Struct {
        for i := 0; i < st.NumField(); i++ {
            fieldVal := sv.Field(i)
            if fieldVal.CanSet() {
                tagStr := st.Field(i).Tag.Get(tagName)
                if len(tagStr) == 0 {
                    continue
                }
                tagList := strings.Split(strings.Replace(tagStr, " ", "", -1), ",")
                //fmt.Println(tagList)
                // ContainsCommonItem checks whether there is at least one common item in arrays
                if !ContainsCommonItem(tagList, acTags) {
                    fieldVal.Set(reflect.Zero(fieldVal.Type()))
                }
            }
        }
    }
}

//ContainsCommonItem checks if arrays have at least one equal item
func ContainsCommonItem(arr1 []string, arr2 []string) bool {
    for i := 0; i < len(arr1); i++ {
        for j := 0; j < len(arr2); j++ {
            if arr1[i] == arr2[j] {
                return true
            }
        }
    }
    return false
}
func main() {
    u := User{ID: 1, Username: "very secret", Nickname: "hinzir"}
    //assume authenticated user doesn't has permission to access private fields
    OmitFields(&u, []string{"public"}) 
    bytes, _ := json.Marshal(&u)
    fmt.Println(string(bytes))


    u2 := User2{ID: 1, Username: "very secret", Nickname: "hinzir"}
    //you want to filter fields by field names
    OmitFields(&u2, []string{"id", "nickname"}) 
    bytes, _ = json.Marshal(&u2)
    fmt.Println(string(bytes))

}

1

Tôi đã tạo hàm này để chuyển đổi struct sang chuỗi JSON bằng cách bỏ qua một số trường. Hy vọng nó sẽ giúp.

func GetJSONString(obj interface{}, ignoreFields ...string) (string, error) {
    toJson, err := json.Marshal(obj)
    if err != nil {
        return "", err
    }

    if len(ignoreFields) == 0 {
        return string(toJson), nil
    }

    toMap := map[string]interface{}{}
    json.Unmarshal([]byte(string(toJson)), &toMap)

    for _, field := range ignoreFields {
        delete(toMap, field)
    }

    toJson, err = json.Marshal(toMap)
    if err != nil {
        return "", err
    }
    return string(toJson), nil
}

Ví dụ: https://play.golang.org/p/nmq7MFF47Gp

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.