From be5a66e0a0e9061d33ec0dece356d7ac552924a9 Mon Sep 17 00:00:00 2001 From: maru44 Date: Mon, 4 Apr 2022 22:34:13 +0900 Subject: [PATCH] encode --- src/encoding/json/encode.go | 16 +++++++- src/encoding/json/encode_test.go | 69 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index fc865386ed37c2..3f8c3607bc916d 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -1239,6 +1239,8 @@ func typeFields(t reflect.Type) structFields { // Scan f.typ for fields to include. for i := 0; i < f.typ.NumField(); i++ { + var spread bool + sf := f.typ.Field(i) if sf.Anonymous { t := sf.Type @@ -1259,6 +1261,18 @@ func typeFields(t reflect.Type) structFields { if tag == "-" { continue } + if tag == "..." { + t := sf.Type + spread = true + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + if !sf.IsExported() && t.Kind() != reflect.Struct { + // Ignore embedded fields of unexported non-struct types. + continue + } + } + name, opts := parseTag(tag) if !isValidTag(name) { name = "" @@ -1287,7 +1301,7 @@ func typeFields(t reflect.Type) structFields { } // Record found field and index sequence. - if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + if !spread && (name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct) { tagged := name != "" if name == "" { name = sf.Name diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index 0b021f0074991d..ac398478bd5716 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -1201,3 +1201,72 @@ func TestMarshalerError(t *testing.T) { } } } + +func TestMarshalSpread(t *testing.T) { + type base[T any] struct { + Name string + Free T `json:"..."` + CreatedAt time.Time + } + + type additional struct { + Age int + IsValid bool + } + + now := time.Now() + + tests := []struct{ + name string + in any + want string + }{ + { + name: "ok", + in: base[additional]{ + Name: "Foo", + Free: additional{ + Age: 20, + } + CreatedAt: now, + }, + want: fmt.Sprintf(`{"Name":"Foo","Age":20,"IsValid":false,"CreatedAt":"%s"}`, now.String()), + }, + { + name: "ok ptr", + in: base[*additional]{ + Name: "Foo", + Free: &additional{ + Age: 40, + IsValid: true, + }, + CreatedAt: now, + } + want: fmt.Sprintf(`{"Name":"Foo","Age":40,"IsValid":true,"CreatedAt":"%s"}`, now.String()), + }, + { + name: "ok ignored", + in: base[string]{ + Name: "Foo", + Free: "ignored...", + CreatedAt: now, + } + want: fmt.Sprintf(`{"Name":"Foo","CreatedAt":"%s"}`, now.String()), + }, + } + + for _, tt := range tests{ + tt := tt + b, err := Marshal(tt.in) + if ok := (err == nil); ok != tt.ok { + if err != nil { + t.Errorf("test %d, unexpected failure: %v", i, err) + } else { + t.Errorf("test %d, unexpected success", i) + } + } + if got := string(b); got != tt.want { + t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) + } + } +}