diff --git a/src/pkg/encoding/xml/marshal.go b/src/pkg/encoding/xml/marshal.go index 7a05a1bb10e..a2e47cf9b83 100644 --- a/src/pkg/encoding/xml/marshal.go +++ b/src/pkg/encoding/xml/marshal.go @@ -52,6 +52,10 @@ const ( // - a field with tag ",comment" is written as an XML comment, not // subject to the usual marshalling procedure. It must not contain // the "--" string within it. +// - a field with a tag including the "omitempty" option is omitted +// if the field value is empty. The empty values are false, 0, any +// nil pointer or interface value, and any array, slice, map, or +// string of length zero. // // If a field uses a tag "a>b>c", then the element c will be nested inside // parent elements a and b. Fields that appear next to each other that name @@ -63,6 +67,8 @@ const ( // FirstName string `xml:"person>name>first"` // LastName string `xml:"person>name>last"` // Age int `xml:"person>age"` +// Height float `xml:"person>height,omitempty"` +// Married bool `xml:"person>married"` // } // // xml.Marshal(&Result{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}) @@ -76,6 +82,7 @@ const ( // Doe // // 42 +// false // // // @@ -116,6 +123,9 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error { if !val.IsValid() { return nil } + if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) { + return nil + } kind := val.Kind() typ := val.Type() @@ -183,12 +193,8 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error { continue } fv := val.FieldByIndex(finfo.idx) - switch fv.Kind() { - case reflect.String, reflect.Array, reflect.Slice: - // TODO: Should we really do this once ,omitempty is in? - if fv.Len() == 0 { - continue - } + if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { + continue } p.WriteByte(' ') p.WriteString(finfo.name) @@ -378,3 +384,21 @@ type UnsupportedTypeError struct { func (e *UnsupportedTypeError) Error() string { return "xml: unsupported type: " + e.Type.String() } + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} diff --git a/src/pkg/encoding/xml/marshal_test.go b/src/pkg/encoding/xml/marshal_test.go index 0f6c0f0795d..ce51ea82b97 100644 --- a/src/pkg/encoding/xml/marshal_test.go +++ b/src/pkg/encoding/xml/marshal_test.go @@ -38,14 +38,14 @@ type NamedType string type Port struct { XMLName struct{} `xml:"port"` - Type string `xml:"type,attr"` + Type string `xml:"type,attr,omitempty"` Comment string `xml:",comment"` Number string `xml:",chardata"` } type Domain struct { XMLName struct{} `xml:"domain"` - Country string `xml:",attr"` + Country string `xml:",attr,omitempty"` Name []byte `xml:",chardata"` Comment []byte `xml:",comment"` } @@ -149,11 +149,33 @@ type NameInField struct { type AttrTest struct { Int int `xml:",attr"` - Lower int `xml:"int,attr"` + Named int `xml:"int,attr"` Float float64 `xml:",attr"` Uint8 uint8 `xml:",attr"` Bool bool `xml:",attr"` Str string `xml:",attr"` + Bytes []byte `xml:",attr"` +} + +type OmitAttrTest struct { + Int int `xml:",attr,omitempty"` + Named int `xml:"int,attr,omitempty"` + Float float64 `xml:",attr,omitempty"` + Uint8 uint8 `xml:",attr,omitempty"` + Bool bool `xml:",attr,omitempty"` + Str string `xml:",attr,omitempty"` + Bytes []byte `xml:",attr,omitempty"` +} + +type OmitFieldTest struct { + Int int `xml:",omitempty"` + Named int `xml:"int,omitempty"` + Float float64 `xml:",omitempty"` + Uint8 uint8 `xml:",omitempty"` + Bool bool `xml:",omitempty"` + Str string `xml:",omitempty"` + Bytes []byte `xml:",omitempty"` + Ptr *PresenceTest `xml:",omitempty"` } type AnyTest struct { @@ -549,13 +571,65 @@ var marshalTests = []struct { { Value: &AttrTest{ Int: 8, - Lower: 9, + Named: 9, Float: 23.5, Uint8: 255, Bool: true, - Str: "s", + Str: "str", + Bytes: []byte("byt"), }, - ExpectXML: ``, + ExpectXML: ``, + }, + { + Value: &AttrTest{Bytes: []byte{}}, + ExpectXML: ``, + }, + { + Value: &OmitAttrTest{ + Int: 8, + Named: 9, + Float: 23.5, + Uint8: 255, + Bool: true, + Str: "str", + Bytes: []byte("byt"), + }, + ExpectXML: ``, + }, + { + Value: &OmitAttrTest{}, + ExpectXML: ``, + }, + + // omitempty on fields + { + Value: &OmitFieldTest{ + Int: 8, + Named: 9, + Float: 23.5, + Uint8: 255, + Bool: true, + Str: "str", + Bytes: []byte("byt"), + Ptr: &PresenceTest{}, + }, + ExpectXML: `` + + `8` + + `9` + + `23.5` + + `255` + + `true` + + `str` + + `byt` + + `` + + ``, + }, + { + Value: &OmitFieldTest{}, + ExpectXML: ``, }, // Test ",any" diff --git a/src/pkg/encoding/xml/read_test.go b/src/pkg/encoding/xml/read_test.go index 833eafc9a58..a3b0b1d594f 100644 --- a/src/pkg/encoding/xml/read_test.go +++ b/src/pkg/encoding/xml/read_test.go @@ -97,7 +97,7 @@ type Entry struct { } type Link struct { - Rel string `xml:"rel,attr"` + Rel string `xml:"rel,attr,omitempty"` Href string `xml:"href,attr"` } @@ -109,7 +109,7 @@ type Person struct { } type Text struct { - Type string `xml:"type,attr"` + Type string `xml:"type,attr,omitempty"` Body string `xml:",chardata"` } diff --git a/src/pkg/encoding/xml/typeinfo.go b/src/pkg/encoding/xml/typeinfo.go index 5475f290d18..8e2e4508b10 100644 --- a/src/pkg/encoding/xml/typeinfo.go +++ b/src/pkg/encoding/xml/typeinfo.go @@ -36,8 +36,7 @@ const ( fComment fAny - // TODO: - //fOmitEmpty + fOmitEmpty fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny ) @@ -133,20 +132,28 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro finfo.flags |= fComment case "any": finfo.flags |= fAny + case "omitempty": + finfo.flags |= fOmitEmpty } } // Validate the flags used. + valid := true switch mode := finfo.flags & fMode; mode { case 0: finfo.flags |= fElement case fAttr, fCharData, fInnerXml, fComment, fAny: - if f.Name != "XMLName" && (tag == "" || mode == fAttr) { - break + if f.Name == "XMLName" || tag != "" && mode != fAttr { + valid = false } - fallthrough default: // This will also catch multiple modes in a single field. + valid = false + } + if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { + valid = false + } + if !valid { return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q", f.Name, typ, f.Tag.Get("xml")) }