union

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 26, 2025 License: MIT Imports: 5 Imported by: 0

README

Union

Generic union types for Go with JSON support.

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

Installation

go get github.com/eriicafes/union

TaggedUnion

TaggedUnion represents a discriminated union where the JSON representation includes a variant field indicating which type is active and a value field containing the data.

Define a tagged union type

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"`
}
Create and use a tagged union
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}
}
JSON marshaling (TaggedUnion)

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}}
JSON unmarshaling (TaggedUnion)
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}
Custom field names

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

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.

Define an untagged union type

Create a struct where each field represents a possible variant. No struct tags are needed.

type Shape struct {
    Circle    *Circle
    Rectangle *Rectangle
    Triangle  *Triangle
}
Create and use an untagged union
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}
}
JSON marshaling (Union)

Union serializes data directly without a wrapper.

shape.Value.Circle = &Circle{Radius: 5.0}

data, _ := json.Marshal(shape)
// {"radius": 5}
JSON unmarshaling (Union)

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}

Error handling

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

Documentation

Overview

Package union provides generic union type implementations for Go with JSON marshaling and unmarshaling support.

This package offers two union types:

  • TaggedUnion: A discriminated union with explicit variant/value wrapper
  • Union: An untagged union that marshals data directly

TaggedUnion

TaggedUnion represents a discriminated union where the JSON representation includes a variant field indicating which type is active and a value field containing the data.

Example usage:

type Shape struct {
    Circle    *Circle    `variant:"circle"`
    Rectangle *Rectangle `variant:"rectangle"`
    Triangle  *Triangle  `variant:"triangle"`
}

var shape union.TaggedUnion[Shape]
shape.Value.Circle = &Circle{Radius: 5.0}

// Marshals to: {"type": "circle", "value": {"radius": 5.0}}
data, _ := json.Marshal(shape)

The `variant` struct tag specifies the variant name in JSON. If no tag is provided, the field name is used (e.g., "Circle" instead of "circle").

Custom field names can be specified by implementing TaggedFieldNames():

func (s Shape) TaggedFieldNames() (variant, value string) {
    return "kind", "data"
}

// Marshals to: {"kind": "circle", "data": {...}}

Union

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.

Example usage:

type Shape struct {
    Circle    *Circle
    Rectangle *Rectangle
    Triangle  *Triangle
}

var shape union.Union[Shape]
shape.Value.Circle = &Circle{Radius: 5.0}

// Marshals to: {"radius": 5.0}
data, _ := json.Marshal(shape)

// Unmarshaling tries each field until one produces a non-zero value
_ = json.Unmarshal([]byte(`{"width": 10, "height": 5}`), &shape)
// shape.Value.Rectangle will be set

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type TaggedUnion

type TaggedUnion[Spec any] struct{ Value Spec }

TaggedUnion represents a discriminated union type that can hold one of several variant types defined in the Spec struct. The Spec type should be a struct where each field represents a possible variant of the union.

Only one field in the Spec struct should be non-zero at any time. When marshaling to JSON, the union is represented as an object with a variant field (indicating which variant is active) and a value field (containing the variant's data).

func (TaggedUnion[Spec]) GetValue

func (u TaggedUnion[Spec]) GetValue() any

GetValue returns the value of the active variant in the union. It iterates through all fields in the Spec struct and returns the value of the non-zero field. If no fields are set or multiple fields are set, it returns nil (indicating an invalid state).

func (TaggedUnion[Spec]) MarshalJSON

func (u TaggedUnion[Spec]) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface. It serializes the union to JSON as an object with two fields:

  • A variant field (default "type") containing the variant name
  • A value field (default "value") containing the variant's data

The variant name is determined by the struct field's `variant` struct tag, or the field name if no variant is specified.

Returns an error if:

  • The Spec type is not a struct
  • No fields are set (zero state)
  • Multiple fields are set (invalid state)

func (*TaggedUnion[Spec]) UnmarshalJSON

func (u *TaggedUnion[Spec]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface. It deserializes JSON data into the union by:

  1. Reading the variant field to determine which variant is active
  2. Unmarshaling the value field into the corresponding struct field

The method handles both pointer and non-pointer fields correctly.

Returns an error if:

  • The JSON data is malformed
  • The Spec type is not a struct
  • The variant or value fields are missing
  • The variant field doesn't match any known variant
  • Multiple struct fields match the same variant (invalid Spec definition)
  • The value cannot be unmarshaled into the target field type

type Union

type Union[Spec any] struct{ Value Spec }

Union represents an untagged union type that can hold one of several variant types defined in the Spec struct. The Spec type should be a struct where each field represents a possible variant of the union.

Only one field in the Spec struct should be non-zero at any time. When marshaling to JSON, the union's data is marshaled directly without a wrapper. When unmarshaling, each field is tried in order until one successfully deserializes to a non-zero value.

func (Union[Spec]) GetValue

func (u Union[Spec]) GetValue() any

GetValue returns the value of the active variant in the union. It iterates through all fields in the Spec struct and returns the value of the non-zero field. If no fields are set or multiple fields are set, it returns nil (indicating an invalid state).

func (Union[Spec]) MarshalJSON

func (u Union[Spec]) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface. It serializes the union's active variant data directly to JSON.

Returns an error if:

  • The Spec type is not a struct
  • No fields are set (zero state)
  • Multiple fields are set (invalid state)

func (*Union[Spec]) UnmarshalJSON

func (u *Union[Spec]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface. It deserializes JSON data into the union by trying each field in order until one successfully unmarshals to a non-zero value. Uses strict matching to ensure all JSON fields map to struct fields.

Returns an error if:

  • The JSON data is malformed
  • The Spec type is not a struct
  • No field successfully unmarshals to a non-zero value

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL