我正在嘗試在 Go 中實作 HAL,只是想看看我是否可以。這意味著我有一個HAL在有效負載上通用的型別,并且還包含_links:
type HAL[T any] struct {
Payload T
Links Linkset `json:"_links,omitempty"`
}
在 HAL 規范中,有效負載實際上位于頂層,而不是嵌套在其中 - 就像 Siren 那樣。所以這意味著給定以下內容:
type TestPayload struct {
Name string `json:"name"`
Answer int `json:"answer"`
}
hal := HAL[TestPayload]{
Payload: TestPayload{
Name: "Graham",
Answer: 42,
},
Links: Linkset{
"self": {
{Href: "/"},
},
},
}
生成的 JSON 應該是:
{
"name": "Graham",
"answer": 42,
"_links": {
"self": {"href": "/"}
}
}
但是我想不出一個讓這個 JSON 編組作業的好方法。
我已經看到將有效負載嵌入為匿名成員的建議,如果它不是通用的,它會很好用。不幸的是,您不能以這種方式嵌入泛型型別,所以這不是首發。
我可能可以撰寫一個MarshalJSON可以完成這項作業的方法,但我想知道是否有任何標準方法可以實作這一目標?
我有這個作業代碼的游樂場鏈接,看看它是否有幫助:https ://go.dev/play/p/lorK5Wv-Tri
干杯
uj5u.com熱心網友回復:
您不能嵌入型別引數T,也不應該嘗試展平輸出 JSON。通過約束Twith any,您實際上是在承認任何型別,但并非所有型別都有欄位可以提升到您的HAL結構中。
這在語意上是不一致的。
如果您嘗試嵌入沒有欄位的型別,這會變得很明顯,從而提供不同的輸出 JSON。以解決方案reflect.StructOf為例,沒有什么能阻止我實體化HAL[[]int]{ Payload: []int{1,2,3}, Links: ... },在這種情況下,輸出將是:
{"X":[1,2,3],"Links":{"self":{"href":"/"}}}
這會使您的 JSON 序列化隨用于實體化的型別發生變化T,這對于閱讀您的代碼的人來說不容易發現。您正在使您的代碼更加模糊和不可預測,并有效地反對型別引數提供的泛化。
使用命名欄位Payload T會更好,因為:
- 輸出 JSON 總是(對于大多數意圖和目的)與實際結構一致
HAL - 解組還保持可預測的行為
- 代碼的可擴展性不是問題,因為您不必重復
HAL構建匿名結構的所有欄位
OTOH,如果您的要求是將結構精確地編組為扁平結構,并且其他所有內容都帶有鍵,那么至少通過檢查實作使其顯而易見reflect.TypeOf(hal.Payload).Kind() == reflect.Struct,MarshalJSON并為其他任何T可能的情況提供默認情況。將不得不在 中重復JSONUnmarshal。
uj5u.com熱心網友回復:
我會制作一個自定義 JSON 編解碼器,_links在為有效負載生成的 JSON 末尾插入欄位。
馬歇爾。
type Link struct {
Href string `json:"href"`
}
type Linkset map[string]Link
type HAL[T any] struct {
Payload T
Links Linkset `json:"_links,omitempty"`
}
func (h HAL[T]) MarshalJSON() ([]byte, error) {
payloadJson, err := json.Marshal(h.Payload)
if err != nil {
return nil, err
}
if len(payloadJson) == 0 {
return nil, fmt.Errorf("Empty payload")
}
if h.Links != nil {
return appendField(payloadJson, "_links", h.Links)
}
return payloadJson, nil
}
func appendField[T any](raw []byte, fieldName string, v T) ([]byte, error) {
// The JSON data must be braced in {}
if raw[0] != '{' || raw[len(raw)-1] != '}' {
return nil, fmt.Errorf("Not an object: %s", string(raw))
}
valJson, err := json.Marshal(v)
if err != nil {
return nil, err
}
// Add the field at the end of the json text
result := bytes.NewBuffer(raw[:len(raw)-1])
// Append `"<fieldName>":value`
// Insert comma if the `raw` object is not empty
if len(raw) > 2 {
result.WriteByte(',')
}
// tag
result.WriteByte('"')
result.WriteString(fieldName)
result.WriteByte('"')
// colon
result.WriteByte(':')
// value
result.Write(valJson)
// closing brace
result.WriteByte('}')
return result.Bytes(), nil
}
Payload如果序列化為 JSON 物件以外的內容,則編組器會回傳錯誤。原因是編解碼器只能_links向物件添加欄位。
解組器:
func (h *HAL[T]) UnmarshalJSON(raw []byte) error {
// Unmarshal fields of the payload first.
// Unmarshal the whole JSON into the payload, it is safe:
// decorer ignores unknow fields and skips "_links".
if err := json.Unmarshal(raw, &h.Payload); err != nil {
return err
}
// Get "_links": scan trough JSON until "_links" field
links := make(Linkset)
exists, err := extractField(raw, "_links", &links)
if err != nil {
return err
}
if exists {
h.Links = links
}
return nil
}
func extractField[T any](raw []byte, fieldName string, v *T) (bool, error) {
// Scan through JSON until field is found
decoder := json.NewDecoder(bytes.NewReader(raw))
t := must(decoder.Token())
// should be `{`
if t != json.Delim('{') {
return false, fmt.Errorf("Not an object: %s", string(raw))
}
t = must(decoder.Token())
if t == json.Delim('}') {
// Empty object
return false, nil
}
for decoder.More() {
name, ok := t.(string)
if !ok {
return false, fmt.Errorf("must never happen: expected string, got `%v`", t)
}
if name != fieldName {
skipValue(decoder)
} else {
if err := decoder.Decode(v); err != nil {
return false, err
}
return true, nil
}
if decoder.More() {
t = must(decoder.Token())
}
}
return false, nil
}
func skipValue(d *json.Decoder) {
braceCnt := 0
for d.More() {
t := must(d.Token())
if t == json.Delim('{') || t == json.Delim('[') {
braceCnt
}
if t == json.Delim('}') || t == json.Delim(']') {
braceCnt--
}
if braceCnt == 0 {
return
}
}
}
unmarshaller 在非物件上也失敗了。需要讀取_links欄位。為此,輸入必須是一個物件。
完整示例:https ://go.dev/play/p/E3NN2T7Fbnm
func main() {
hal := HAL[TestPayload]{
Payload: TestPayload{
Name: "Graham",
Answer: 42,
},
Links: Linkset{
"self": Link{Href: "/"},
},
}
bz := must(json.Marshal(hal))
println(string(bz))
var halOut HAL[TestPayload]
err := json.Unmarshal(bz, &halOut)
if err != nil {
println("Decode failed: ", err.Error())
}
fmt.Printf("%#v\n", halOut)
}
輸出:
{"name":"Graham","answer":42,"_links":{"self":{"href":"/"}}}
main.HAL[main.TestPayload]{Payload:main.TestPayload{Name:"Graham", Answer:42}, Links:main.Linkset{"self":main.Link{Href:"/"}}}
uj5u.com熱心網友回復:
是的,嵌入是最簡單的方法,正如您所寫,您目前無法嵌入型別引數。
但是,您可以構造一個使用反射嵌入型別引數的型別。我們可以實體化這種型別并對其進行編組。
例如:
func (hal HAL[T]) MarshalJSON() ([]byte, error) {
t := reflect.StructOf([]reflect.StructField{
{
Name: "X",
Anonymous: true,
Type: reflect.TypeOf(hal.Payload),
},
{
Name: "Links",
Type: reflect.TypeOf(hal.Links),
},
})
v := reflect.New(t).Elem()
v.Field(0).Set(reflect.ValueOf(hal.Payload))
v.Field(1).Set(reflect.ValueOf(hal.Links))
return json.Marshal(v.Interface())
}
這將輸出(在Go Playground上嘗試):
{"name":"Graham","answer":42,"Links":{"self":{"href":"/"}}}
請參閱相關:將任意欄位添加到未知結構的 json 輸出
uj5u.com熱心網友回復:
把事情簡單化。
是的,嵌入型別會很好 - 但由于目前(截至go1.19)無法嵌入泛型型別 - 只需行內寫出:
body, _ = json.Marshal(
struct {
TestPayload
Links Linkset `json:"_links,omitempty"`
}{
TestPayload: hal.Payload,
Links: hal.Links,
},
)
https://go.dev/play/p/8yrB-MzUVK-
{
"name": "Graham",
"answer": 42,
"_links": {
"self": {
"href": "/"
}
}
}
是的,約束型別需要被參考兩次——但是所有的自定義都是代碼本地化的,所以不需要自定義封送器。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/514595.html
標籤:json去仿制药编组
上一篇:Maven堅持JAVA_HOME定義不正確,除非我以sudo運行它
下一篇:為什么reflect.TypeOf(new(Encoder)).Elem()!=reflect.TypeOf(interfaceVariable)?
