慕的地10843
是的,不幸的是你不能嵌入类型参数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构建匿名结构的所有字段OTOH,如果您的要求恰好是将结构编组为扁平化,而其他所有内容都带有键(HAL 类型可能就是这种情况),至少通过检查实现使其显而易见,并为任何情况提供reflect.ValueOf(hal.Payload).Kind() == reflect.Struct默认MarshalJSON情况否则T可能。将不得不在JSONUnmarshal.T这是一个带有反射的解决方案,当您将更多字段添加到主结构时,它可以在不是结构时工作并缩放:// necessary to marshal HAL without causing infinite loop// can't declare inside the method due to a current limitation with Go genericstype tmp[T any] HAL[T]func (h HAL[T]) MarshalJSON() ([]byte, error) { // examine Payload, if it isn't a struct, i.e. no embeddable fields, marshal normally v := reflect.ValueOf(h.Payload) if v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface { v = v.Elem() } if v.Kind() != reflect.Struct { return json.Marshal(tmp[T](h)) } // flatten all fields into a map m := make(map[string]any) // flatten Payload first for i := 0; i < v.NumField(); i++ { key := jsonkey(v.Type().Field(i)) m[key] = v.Field(i).Interface() } // flatten the other fields w := reflect.ValueOf(h) // start at 1 to skip the Payload field for i := 1; i < w.NumField(); i++ { key := jsonkey(w.Type().Field(i)) m[key] = w.Field(i).Interface() } return json.Marshal(m)}func jsonkey(field reflect.StructField) string { // trickery to get the json tag without omitempty and whatnot tag := field.Tag.Get("json") tag, _, _ = strings.Cut(tag, ",") if tag == "" { tag = field.Name } return tag}用HAL[TestPayload]orHAL[*TestPayload]它输出:{"answer":42,"name":"Graham","_links":{"self":{"href":"/"}}}用HAL[[]int]它输出:{"Payload":[1,2,3],"_links":{"self":{"href":"/"}}}游乐场:https://go.dev/play/p/bWGXWj_rC5F
尚方宝剑之说
我会制作一个自定义 JSON 编解码器,_links在为有效负载生成的 JSON 末尾插入字段。编组器。type Link struct { Href string `json:"href"`}type Linkset map[string]Linktype 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 } }}解组器在非对象上也会失败。需要读取_links字段。为此,输入必须是一个对象。完整示例:https://go.dev/play/p/E3NN2T7Fbnmfunc 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:"/"}}}
牧羊人nacy
是的,嵌入是最简单的方法,正如您所写,您目前无法嵌入类型参数。但是,您可以构造一个使用反射嵌入类型参数的类型。我们可以实例化此类型并对其进行编组。例如: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":"/"}}}