Deferred JSON Parsing with json.RawMessage in Go
Use json.RawMessage to defer JSON parsing in Go. Handle polymorphic payloads, conditional decoding, and performance optimization for large documents.
Detailed Explanation
Deferred Parsing with json.RawMessage
json.RawMessage is a raw encoded JSON value. It implements json.Marshaler and json.Unmarshaler, allowing you to delay parsing part of a JSON document.
The Problem
{
"type": "user.created",
"payload": { "id": 1, "name": "Alice" }
}
{
"type": "order.placed",
"payload": { "order_id": 99, "total": 49.99 }
}
The payload shape depends on type. You cannot define a single struct for it.
Solution: Two-Phase Parsing
type Event struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"`
}
Phase 1 — Parse the envelope:
var event Event
json.Unmarshal(data, &event)
Phase 2 — Parse the payload based on type:
switch event.Type {
case "user.created":
var user UserPayload
json.Unmarshal(event.Payload, &user)
case "order.placed":
var order OrderPayload
json.Unmarshal(event.Payload, &order)
}
Performance Benefits
For large JSON documents where you only need a few fields, json.RawMessage avoids parsing unused sections entirely:
type LargeDoc struct {
Header Header `json:"header"`
Details json.RawMessage `json:"details"`
Audit json.RawMessage `json:"audit"`
}
You parse Header immediately and only unmarshal Details or Audit when needed.
Forwarding JSON Unchanged
json.RawMessage is also useful when you need to pass JSON through without modifying it:
type Proxy struct {
Route string `json:"route"`
Body json.RawMessage `json:"body"`
}
The Body is stored as-is and can be re-serialized without any parsing or data loss.
Null Handling
A JSON null is valid json.RawMessage content. Check with:
if string(event.Payload) == "null" {
// handle null payload
}
When to Use json.RawMessage
- Polymorphic payloads (webhooks, event systems)
- Partial parsing for performance
- JSON passthrough / proxying
- Schema-less storage (store raw JSON in a database)
Use Case
Event-driven architectures and webhook handlers must process messages with varying payload structures. json.RawMessage lets you inspect the event type first and then parse the payload into the correct Go type.