Union provides generic union type implementations for Go with JSON marshaling and unmarshaling support:
- TaggedUnion: A discriminated union with explicit variant/value wrapper
- Union: An untagged union that marshals data directly
go get github.com/eriicafes/unionTaggedUnion represents a discriminated union where the JSON representation includes a variant field indicating which type is active and a value field containing the data.
Create a struct where each field represents a possible variant. Use variant struct tags to customize variant names in JSON.
package main
import "github.com/eriicafes/union"
type Shape struct {
Circle *Circle `variant:"circle"`
Rectangle *Rectangle `variant:"rectangle"`
Triangle *Triangle `variant:"triangle"`
}
type Circle struct {
Radius float64 `json:"radius"`
}
type Rectangle struct {
Width float64 `json:"width"`
Height float64 `json:"height"`
}
type Triangle struct {
Base float64 `json:"base"`
Height float64 `json:"height"`
}func main() {
var shape union.TaggedUnion[Shape]
// Set the active variant
shape.Value.Circle = &Circle{Radius: 5.0}
// Get the active value
value := shape.GetValue() // returns *Circle{Radius: 5.0}
}TaggedUnion serializes to JSON with a type field indicating which variant is active and a value field containing the variant's data.
shape.Value.Circle = &Circle{Radius: 5.0}
data, _ := json.Marshal(shape)
// {"type": "circle", "value": {"radius": 5}}jsonData := []byte(`{"type": "rectangle", "value": {"width": 10, "height": 5}}`)
var shape union.TaggedUnion[Shape]
json.Unmarshal(jsonData, &shape)
// shape.Value.Rectangle is now set to &Rectangle{Width: 10, Height: 5}Implement the TaggedFieldNames method to customize the JSON field names.
func (s Shape) TaggedFieldNames() (variant, value string) {
return "kind", "data"
}
// Marshals to: {"kind": "circle", "data": {...}}Union represents an untagged union where the JSON representation is the data itself, without any wrapper. When unmarshaling, each field is tried in order until one successfully deserializes to a non-zero value.
Create a struct where each field represents a possible variant. No struct tags are needed.
type Shape struct {
Circle *Circle
Rectangle *Rectangle
Triangle *Triangle
}func main() {
var shape union.Union[Shape]
// Set the active variant
shape.Value.Circle = &Circle{Radius: 5.0}
// Get the active value
value := shape.GetValue() // returns *Circle{Radius: 5.0}
}Union serializes data directly without a wrapper.
shape.Value.Circle = &Circle{Radius: 5.0}
data, _ := json.Marshal(shape)
// {"radius": 5}Union tries each field in order until one successfully deserializes to a non-zero value.
jsonData := []byte(`{"width": 10, "height": 5}`)
var shape union.Union[Shape]
json.Unmarshal(jsonData, &shape)
// shape.Value.Rectangle is now set to &Rectangle{Width: 10, Height: 5}Both union types enforce invariants and return errors when:
- No variant is set (zero state)
- Multiple variants are set (invalid state)
- JSON data is malformed
TaggedUnion additionally returns errors when:
- The variant field doesn't match any known variant
- The variant or value fields are missing
Union additionally returns errors when:
- No field successfully unmarshals to a non-zero value