skip to content

Why can encoding/xml not fill a map field, and what struct shape do you use instead?

level: middleimportance: nice to knowfreq 28%

answer

  1. the package refuses one Go type here
  2. XML has no one right key/value encoding
  3. keys may repeat, order matters
  4. model the repeated element as a slice
  5. attribute for the key, text for the value

basics

~20 s

encoding/xml has no map support: xml.Marshal reports an unsupported type for a map, and Unmarshal has no rule for filling one. Decode repeated key/value elements into a slice of small structs, then build the map yourself.

solid answer

~40 s

Maps are simply not part of the package's data model. `xml.Marshal` refuses a map outright with an `*xml.UnsupportedTypeError`, and `xml.Unmarshal` has no rule that would populate a map field, so a `map[string]string` in a wire struct is a dead end. The reason is that XML gives no canonical map encoding: a key could be an element name, an attribute value or text, keys may repeat, and element order is significant — the package declines to guess. The idiomatic shape for a vendor's `<property name="colour">red</property>` list is a slice of a two-field struct, one field tagged `name,attr` and one tagged `,chardata`, which preserves order and duplicates. If a map is what your code wants, build it in a short loop after decoding, deciding explicitly what a duplicate key means.

code

go · 8 lines
go
type Property struct {
	Name string `xml:"name,attr"` // <property name="colour">
	Value string `xml:",chardata"` // red
}

type Config struct {
	Properties []Property `xml:"property"`
}

go deeper

for a junior

Remember that a map field in a struct handed to encoding/xml does not work, and that the fix is a slice of small structs with the key as an attribute and the value as character data.

for a middle

Explain why the package refuses: XML has several plausible key/value encodings, element names are constrained, and lists are ordered and may repeat. Then show the slice-plus-loop conversion.

for a senior

Make the duplicate-key and ordering policy explicit and testable when converting a third-party feed, and be able to say what your converter does when the vendor sends the same key twice.

for a principal

Decide how far a wire type should mirror a document you do not control versus being reshaped for consumers, and who owns the policy when a feed starts carrying data the convenient mapping would have silently lost.

## The rule `encoding/xml` does not handle Go maps. `xml.Marshal` returns an `*xml.UnsupportedTypeError` when asked to encode one, and `xml.Unmarshal` has no matching rule that could fill a map field. There is no tag option that turns one on. This surprises people because reflective codecs for other formats usually do support maps, and because a `<property name="…">value</property>` list obviously *means* a map. ## Why the package declines XML has no canonical encoding of a key/value collection, and every plausible choice loses something: - **Element name as key.** `<colour>red</colour>` is natural, but XML element names are not arbitrary strings — they cannot start with a digit, cannot contain spaces, and are namespace-qualified. A map key like `"total price"` has no legal element name, so the encoding would be lossy in one direction and would fail unpredictably in the other. - **Attribute as key.** `<property name="colour">red</property>` is what real schemas use, but the element name (`property`), the key attribute name (`name`) and the location of the value are all conventions the document chooses. The package cannot know them. - **Duplicates and order.** XML lists are ordered and may repeat a key; a Go map is unordered and cannot hold a duplicate. Any automatic mapping silently discards information. Rather than pick one convention and be wrong most of the time, the package leaves the decision to you — which is consistent with the rest of it: `encoding/xml` never guesses about document shape. ## The shape to use instead Model the wire exactly, then convert: ```go type Property struct { Name string `xml:"name,attr"` Value string `xml:",chardata"` } type Config struct { Properties []Property `xml:"property"` } ``` A slice field collects every occurrence of the repeated element in document order, so nothing is lost. Building the map afterwards is three lines, and — this is the actual benefit — it forces you to decide what a repeated key means: ```go m := make(map[string]string, len(cfg.Properties)) for _, p := range cfg.Properties { if _, dup := m[p.Name]; dup { return fmt.Errorf("duplicate property %q", p.Name) } m[p.Name] = p.Value } ``` Last-write-wins, first-write-wins, or reject: with a map field the codec would have made that choice for you, invisibly. ## When the key is the element name If the vendor's document uses element names as keys — an open-ended set of children whose names you do not know ahead of time — the `,any` option is the tool. A field tagged `,any` receives sub-elements that matched no other field, so a slice of a struct with an `XMLName` field and a `,chardata` field captures both the name and the text of each unknown child. From there the loop is the same. ## What this costs and what it buys The cost is a few lines of conversion per document and a second type: a wire struct that mirrors the XML, and whatever your program actually wants. The benefit is that every lossy decision — duplicate keys, ordering, keys that are not legal element names — is written down in your code where a reviewer can see it, instead of being buried in a codec's default. For a converter reading a third-party feed, that is usually the trade you want anyway, because the vendor will eventually send you something the naive mapping would have quietly mangled.

  • What if the vendor uses the element name itself as the key, with children you cannot enumerate?
    Use a field tagged `,any`, which receives every sub-element no other field claimed. Make it a slice of a struct with an `XMLName` field of type `xml.Name` to capture each child's name and a `,chardata` field for its text, then fold that slice into whatever collection your code needs.
  • Is there any advantage to converting to a map by hand rather than having the codec do it?
    Yes: it puts the lossy decisions in your code. Duplicate keys, document order and keys that are not legal element names all have to be handled explicitly, and a reviewer can see the policy. A codec-level mapping would resolve all three silently and differently from what you intended.

saying these in an interview costs you the question

  • Assumes a map field just works because other codecs support maps
  • Expects a map field to be filled silently rather than rejected
  • Ignores that XML lists are ordered and may repeat a key
  • Thinks a tag option exists to enable map decoding
  • Uses element names as keys without checking they are legal XML names