diff --git a/encode_test.go b/encode_test.go index ede05684..864be130 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1703,6 +1703,64 @@ func TestAnonymousFields(t *testing.T) { return S{} }, want: `{}`, + }, { + // Structs with an inline tag option should be embedded. + label: "InlinedStruct", + makeInput: func() interface{} { + type ( + S2 struct{ X int } + S struct { + S2 `json:",inline"` + } + ) + return S{S2: S2{X: 1}} + }, + want: `{"X":1}`, + }, { + // Pointers to structs with an inline tag option should be embedded. + label: "InlinedStructPointer", + makeInput: func() interface{} { + type ( + S2 struct{ X int } + S struct { + *S2 `json:",inline"` + } + ) + return S{S2: &S2{X: 1}} + }, + want: `{"X":1}`, + }, { + // Non-anymous generic struct with an inline tag option should + // be embedded. + label: "InlinedStructGeneric", + makeInput: func() interface{} { + type ( + S1 struct { + X int + } + S2 struct { + Y int + } + S[T any] struct { + Embedded T `json:",inline"` + S2 + } + ) + return S[S1]{Embedded: S1{X: 1}, S2: S2{Y: 2}} + }, + want: `{"X":1,"Y":2}`, + }, { + // Non-struct with an inline tag option should be serialized + label: "InlinedStructPointer", + makeInput: func() interface{} { + type ( + S struct { + X int `json:",inline"` + } + ) + return S{X: 1} + }, + want: `{"X":1}`, }} for i, tt := range tests { diff --git a/internal/encoder/code.go b/internal/encoder/code.go index 5b08faef..cfb3424c 100644 --- a/internal/encoder/code.go +++ b/internal/encoder/code.go @@ -1015,6 +1015,9 @@ func isEmbeddedStruct(field *StructFieldCode) bool { if !field.isAnonymous { return false } + if field.tag.IsInline { + return true + } t := field.typ if t.Kind() == reflect.Ptr { t = t.Elem() diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go index b1076368..3da56e84 100644 --- a/internal/encoder/compiler.go +++ b/internal/encoder/compiler.go @@ -637,7 +637,7 @@ func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTa key: tag.Key, tag: tag, offset: field.Offset, - isAnonymous: field.Anonymous && !tag.IsTaggedKey && toElemType(fieldType).Kind() == reflect.Struct, + isAnonymous: (field.Anonymous || tag.IsInline) && !tag.IsTaggedKey && toElemType(fieldType).Kind() == reflect.Struct, isTaggedKey: tag.IsTaggedKey, isNilableType: c.isNilableType(fieldType), isNilCheck: true, diff --git a/internal/runtime/struct_field.go b/internal/runtime/struct_field.go index baab0c59..d34e0a63 100644 --- a/internal/runtime/struct_field.go +++ b/internal/runtime/struct_field.go @@ -31,6 +31,7 @@ func IsIgnoredStructField(field reflect.StructField) bool { type StructTag struct { Key string + IsInline bool IsTaggedKey bool IsOmitEmpty bool IsString bool @@ -84,6 +85,8 @@ func StructTagFromField(field reflect.StructField) *StructTag { st.IsOmitEmpty = true case "string": st.IsString = true + case "inline": + st.IsInline = true } } }