encoding/xml, gob & Binary Formats
JSON covers most HTTP APIs, but Go also ships encoding/xml and encoding/gob for legacy feeds and Go-native binary caches.
Protobuf and other binary codecs sit beside the stdlib when contracts cross languages or need compact wire sizes.
Summary
Pick the serializer to match the consumer: browsers and public REST stay on JSON; enterprise XML integrations use encoding/xml; Go-only process boundaries can use gob; gRPC services use protobuf.
Each format has different compatibility rules, security considerations, and debugging ergonomics.
Recipe
Quick-reference recipe card - copy-paste ready.
// XML marshal
type Feed struct {
XMLName xml.Name `xml:"feed"`
Title string `xml:"title"`
Items []Item `xml:"item"`
}
b, err := xml.Marshal(Feed{Title: "News"})
// gob register + encode (Go-to-Go only)
gob.Register(User{})
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
_ = enc.Encode(User{ID: 1})When to reach for this:
- Partner systems require XML envelopes or SOAP-style payloads.
- Two Go processes share a cache file or pipe with no non-Go readers.
- Internal microservices already standardize on gRPC/protobuf binaries.
Working Example
package main
import (
"bytes"
"encoding/gob"
"encoding/xml"
"fmt"
)
type Item struct {
ID int `xml:"id" json:"id"`
Label string `xml:"label" json:"label"`
}
type Catalog struct {
XMLName xml.Name `xml:"catalog"`
Items []Item `xml:"item"`
}
type CacheEntry struct {
Key string
Items []Item
}
func main() {
cat := Catalog{Items: []Item{{ID: 1, Label: "bolt"}}}
xmlBytes, _ := xml.MarshalIndent(cat, "", " ")
fmt.Println(string(xmlBytes))
gob.Register(CacheEntry{})
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
_ = enc.Encode(CacheEntry{Key: "parts", Items: cat.Items})
dec := gob.NewDecoder(&buf)
var restored CacheEntry
_ = dec.Decode(&restored)
fmt.Println(restored.Items[0].Label)
}What this demonstrates:
xml.NameonXMLNamesets the root element.- Struct tags can differ per format on the same struct when shapes align.
gob.Registeris required for interface values and non-exported concrete types in some cases.- gob round-trips preserve Go types but are not human-readable.
Deep Dive
encoding/xml
XML tags mirror JSON tags: xml:"field", xml:"-", xml:",attr" for attributes, xml:",chardata" for text nodes.
xml.Unmarshal is strict about matching start elements; namespaces may require xmlns attributes on structs.
Use XML when:
- Integrating with legacy enterprise buses, RSS/Atom, or government schemas.
- A WSDL or XSD contract already exists.
Avoid XML for new public mobile or browser APIs unless required.
encoding/gob
gob encodes Go type information on the wire.
Both encoder and decoder must agree on registered types when interfaces are involved.
gob is not suitable for:
- Browser clients.
- Polyglot microservices.
- Long-term storage when Go types may rename fields.
gob fits:
- Caching computed structs locally between two Go binaries you deploy together.
net/rpcstyle internal tooling (legacy; prefer gRPC for new work).
Binary formats beyond stdlib
| Format | Package / tool | Cross-language | Schema |
|---|---|---|---|
| Protobuf | google.golang.org/protobuf | Yes | .proto files |
| MessagePack | third-party | Yes | Informal |
| CBOR | third-party | Yes | Informal |
| gob | encoding/gob | Go only | Go types |
google.golang.org/grpc uses protobuf on HTTP/2; see gRPC Basics for codegen workflow.
Go Notes
// Prefer separate DTOs when XML and JSON shapes diverge
type UserXML struct { /* xml tags */ }
type UserJSON struct { /* json tags */ }Mixing many tag families on one mega-struct complicates reviews; split when wire shapes differ materially.
Gotchas
- gob without Register - Encoding interface values panics or fails at runtime. Fix: call
gob.Registerfor concrete types at init. - XML namespace surprises - Missing
XMLNameor xmlns tags break unmarshal. Fix: mirror the partner sample XML exactly in tests. - gob and renamed fields - gob tracks field names, not tags; renames break cached blobs. Fix: version cache keys or stick to JSON/protobuf for durable storage.
- Double-encoding binary - Stuffing gob bytes into JSON as strings without base64 breaks clients. Fix: use base64 in JSON or pick one format per endpoint.
- XML attr vs element - Same field name in attr and child element collides. Fix: separate structs or explicit tag forms.
- Security: billion laughs - XML parsers can be abused with expansive entities. Fix: limit reader size, disable DTDs if your parser allows, timeout reads.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| JSON | Human debugging, HTTP APIs | Ultra-compact internal RPC |
| Protobuf/gRPC | Typed cross-service contracts | Quick scripts |
| gob | Same-repo Go cache | Any non-Go consumer |
| XML | Mandated partner format | Greenfield mobile clients |
| CSV | Tabular exports | Nested structures |
FAQs
Can one struct carry json and xml tags together?
Yes when shapes align.
Split DTOs when attribute vs element layouts diverge.
Is gob faster than JSON?
Often yes for Go-to-Go paths because it avoids text parsing.
Measure on your payload sizes before committing.
Does encoding/xml support streaming?
Use xml.NewDecoder and xml.NewEncoder for large documents.
Same pattern as JSON decoders.
Should internal queues use gob?
Prefer protobuf or JSON with schemas for anything persisted or inspected by operators.
gob is best for ephemeral process-local caches.
How does protobuf compare to gob?
Protobuf has versioned field numbers and cross-language generators.
gob is simpler but Go-specific and type-coupled.
Can TinyGo use encoding/xml?
Verify at build for your board; XML reflection may be heavier than JSON.
Check stack footer targets.
What about BSON for MongoDB?
Drivers handle BSON; you usually work with Go structs and tags at the application layer.
See database driver docs for tag conventions.
Is net/rpc still recommended?
Not for new systems.
Use gRPC with protobuf for typed RPC.
How do I debug gob payloads?
You cannot read them in a text editor.
Log struct values before encode or use JSON in dev-only endpoints.
Do I need content types per format?
Yes - set Content-Type to application/json, application/xml, or gRPC's binary types.
Clients rely on headers for decode choice.
Related
- Serialization in Go: JSON First, Formats on Demand - format selection overview
- gRPC Basics - protobuf codegen
- Protocol Buffers Schema Design - durable binary contracts
- Struct Tags for JSON, DB & Validation - multi-format tags
- Serialization Best Practices - format policy per boundary
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).