1
0
mirror of https://github.com/golang/go synced 2024-11-25 11:07:59 -07:00

encoding/xml: call MarshalXML(), MarshalXMLAttr(), and MarshalText() defined with pointer receivers even for non-addressable values of non-pointer types on marshalling XML

This commit is contained in:
Dmitry Zenovich 2024-08-17 07:25:13 +03:00
parent f0d880e25c
commit 928e3d925d
2 changed files with 140 additions and 28 deletions

View File

@ -451,22 +451,25 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
if val.CanInterface() && typ.Implements(marshalerType) {
return p.marshalInterface(val.Interface().(Marshaler), defaultStart(typ, finfo, startTemplate))
}
var pv reflect.Value
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(marshalerType) {
return p.marshalInterface(pv.Interface().(Marshaler), defaultStart(pv.Type(), finfo, startTemplate))
}
pv = val.Addr()
} else {
pv = reflect.New(typ)
pv.Elem().Set(val)
}
if pv.CanInterface() && pv.Type().Implements(marshalerType) {
return p.marshalInterface(pv.Interface().(Marshaler), defaultStart(pv.Type(), finfo, startTemplate))
}
// Check for text marshaler.
if val.CanInterface() && typ.Implements(textMarshalerType) {
return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), defaultStart(typ, finfo, startTemplate))
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), defaultStart(pv.Type(), finfo, startTemplate))
}
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), defaultStart(pv.Type(), finfo, startTemplate))
}
// Slices and arrays iterate over the elements. They do not have an enclosing tag.
@ -589,18 +592,23 @@ func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value)
return nil
}
var pv reflect.Value
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) {
attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name)
if err != nil {
return err
}
if attr.Name.Local != "" {
start.Attr = append(start.Attr, attr)
}
return nil
pv = val.Addr()
} else {
pv = reflect.New(val.Type())
pv.Elem().Set(val)
}
if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) {
attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name)
if err != nil {
return err
}
if attr.Name.Local != "" {
start.Attr = append(start.Attr, attr)
}
return nil
}
if val.CanInterface() && val.Type().Implements(textMarshalerType) {
@ -612,16 +620,13 @@ func (p *printer) marshalAttr(start *StartElement, name Name, val reflect.Value)
return nil
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
text, err := pv.Interface().(encoding.TextMarshaler).MarshalText()
if err != nil {
return err
}
start.Attr = append(start.Attr, Attr{name, string(text)})
return nil
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) {
text, err := pv.Interface().(encoding.TextMarshaler).MarshalText()
if err != nil {
return err
}
start.Attr = append(start.Attr, Attr{name, string(text)})
return nil
}
// Dereference or skip nil pointer, interface values.

View File

@ -6,6 +6,7 @@ package xml
import (
"bytes"
"encoding"
"errors"
"fmt"
"io"
@ -2589,3 +2590,109 @@ func TestClose(t *testing.T) {
})
}
}
type structWithMarshalXML struct{ V int }
func (s *structWithMarshalXML) MarshalXML(e *Encoder, _ StartElement) error {
_ = e.EncodeToken(StartElement{Name: Name{Local: "marshalled"}})
_ = e.EncodeToken(CharData(strconv.Itoa(s.V)))
_ = e.EncodeToken(EndElement{Name: Name{Local: "marshalled"}})
return nil
}
var _ = Marshaler(&structWithMarshalXML{})
type embedderX struct {
V structWithMarshalXML
}
func TestMarshalXMLWithPointerXMLMarshalers(t *testing.T) {
for _, test := range []struct {
name string
v interface{}
expected string
}{
{name: "a value with MarshalXML", v: structWithMarshalXML{V: 1}, expected: `<marshalled>1</marshalled>`},
{name: "pointer to a value with MarshalXML", v: &structWithMarshalXML{V: 1}, expected: "<marshalled>1</marshalled>"},
{name: "a struct with a value with MarshalXML", v: embedderX{V: structWithMarshalXML{V: 1}}, expected: "<embedderX><marshalled>1</marshalled></embedderX>"},
{name: "a slice of structs with a value with MarshalXML", v: []embedderX{{V: structWithMarshalXML{V: 1}}}, expected: `<embedderX><marshalled>1</marshalled></embedderX>`},
} {
test := test
t.Run(test.name, func(t *testing.T) {
result, err := Marshal(test.v)
if err != nil {
t.Fatalf("Marshal error: %v", err)
}
if string(result) != test.expected {
t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", result, test.expected)
}
})
}
}
type structWithMarshalText struct{ V int }
func (s *structWithMarshalText) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("marshalled(%d)", s.V)), nil
}
var _ = encoding.TextMarshaler(&structWithMarshalText{})
type embedderT struct {
V structWithMarshalText
}
func TestMarshalXMLWithPointerTextMarshalers(t *testing.T) {
for _, test := range []struct {
name string
v interface{}
expected string
}{
{name: "a value with MarshalText", v: structWithMarshalText{V: 1}, expected: "<structWithMarshalText>marshalled(1)</structWithMarshalText>"},
{name: "pointer to a value with MarshalText", v: &structWithMarshalText{V: 1}, expected: "<structWithMarshalText>marshalled(1)</structWithMarshalText>"},
{name: "a struct with a value with MarshalText", v: embedderT{V: structWithMarshalText{V: 1}}, expected: "<embedderT><V>marshalled(1)</V></embedderT>"},
{name: "a slice of structs with a value with MarshalText", v: []embedderT{{V: structWithMarshalText{V: 1}}}, expected: "<embedderT><V>marshalled(1)</V></embedderT>"},
} {
test := test
t.Run(test.name, func(t *testing.T) {
result, err := Marshal(test.v)
if err != nil {
t.Fatalf("Marshal error: %v", err)
}
if string(result) != test.expected {
t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", result, test.expected)
}
})
}
}
type structWithMarshalXMLAttr struct{ v int }
func (s *structWithMarshalXMLAttr) MarshalXMLAttr(name Name) (Attr, error) {
return Attr{Name: Name{Local: "marshalled"}, Value: strconv.Itoa(s.v)}, nil
}
var _ = MarshalerAttr(&structWithMarshalXMLAttr{})
type embedderAT struct {
X structWithMarshalXMLAttr `xml:"X,attr"`
T structWithMarshalText `xml:"T,attr"`
XP *structWithMarshalXMLAttr `xml:"XP,attr"`
XT *structWithMarshalText `xml:"XT,attr"`
}
func TestMarshalXMLWithPointerAttrMarshalers(t *testing.T) {
result, err := Marshal(embedderAT{
X: structWithMarshalXMLAttr{1},
T: structWithMarshalText{2},
XP: &structWithMarshalXMLAttr{3},
XT: &structWithMarshalText{4},
})
if err != nil {
t.Fatalf("Marshal error: %v", err)
}
expected := `<embedderAT marshalled="1" T="marshalled(2)" marshalled="3" XT="marshalled(4)"></embedderAT>`
if string(result) != expected {
t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", result, expected)
}
}