tagged

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 24, 2026 License: 0BSD Imports: 2 Imported by: 0

README

tagged


tagged is a small Go helper for encoding and decoding tagged unions.

It supports payloads shaped like either:

{ "kind": "...", "body": { ... } }

or:

{ "kind": "...", "...": "variant-specific fields" }

In both cases, kind selects the concrete payload type.

What it provides

  • Registry[K]: maps a tag/key to a constructor for a concrete type.
  • Union[K, P]: holds the decoded tag (Type) and payload (Value).
  • MarshalWith: encodes a union using any serializer with Marshal/Unmarshal.
  • UnmarshalWith: decodes kind, picks a constructor from the registry, and stores pointer payloads as values (Foo instead of *Foo) for convenient assertions.
  • UnmarshalPtrWith: same decode flow, but stores pointer payloads as pointers (*Foo) for oneof interfaces and type switches.
  • Codec / NewCodec: bind a serializer once for repeated operations with the same union config.
  • InlineKeys: built-in key layout for flattened payloads ({"kind":"...","...":...}).

Decode modes

Use UnmarshalWith when you prefer value assertions:

var u tagged.Union[Kind, provider]
_ = tagged.UnmarshalWith[Kind, provider, tagged.DefaultKeys](ser, data, &u)

v, ok := u.Value.(Created) // value type

Use UnmarshalPtrWith when you need pointer semantics (recommended for oneof and type switches):

var u tagged.Union[Kind, provider]
_ = tagged.UnmarshalPtrWith[Kind, provider, tagged.DefaultKeys](ser, data, &u)

switch v := u.Value.(type) {
case *Created:
	_ = v
case *Deleted:
	_ = v
default:
	// unknown
}

Nested vs inline payload shape

The payload shape is controlled by the Keys type used with MarshalWith / Unmarshal*With.

  • tagged.DefaultKeys: nested payload (kind + body)
  • tagged.InlineKeys: flattened payload (kind + inline fields)

Example (flattened):

var u tagged.Union[Kind, provider]
_ = tagged.UnmarshalWith[Kind, provider, tagged.InlineKeys](ser, data, &u)

When you repeatedly encode/decode the same union shape, bind the serializer once:

codec := tagged.NewCodec[Kind, provider, tagged.InlineKeys](ser)

var u tagged.Union[Kind, provider]
_ = codec.UnmarshalPtr(data, &u)

You can also define custom key names and opt into flattened payloads by adding:

func (myKeys) InlinePayload() bool { return true }

BodyKeyName() is ignored in inline mode (it remains part of the interface for backwards compatibility).

Notes

  • Works with any codec abstraction that satisfies Serializer (JSON, YAML, TOML, CBOR, etc.).
  • Field names default to kind and body, but can be customized via the Keys interface.
  • Inline mode requires payloads to encode as objects (structs/maps). Scalar payloads cannot be flattened.

Documentation

Overview

Package tagged provides small helpers for encoding and decoding tagged unions.

A tagged union payload includes a discriminator field (i.e. "kind") that selects the concrete payload type. This package supports both nested payloads:

{ "kind": "...", "body": { ... } }

and inlined/flattened payloads:

{ "kind": "...", ...variant-specific fields... }

Callers provide a Registry via a Provider to map discriminator values to constructors. MarshalWith, UnmarshalWith, and UnmarshalPtrWith work with any codec that satisfies Serializer, and key names/layout are controlled by a Keys type (for example, DefaultKeys or InlineKeys).

For repeated operations with the same serializer and union layout, use Codec via NewCodec to bind the serializer once and reduce call-site boilerplate.

For decoding failures, callers can use errors.As to inspect typed errors such as MissingKindError, UnknownKindError, and BadPayloadShapeError.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MarshalWith

func MarshalWith[K comparable, P Provider[K], J Keys](s Serializer, u Union[K, P]) ([]byte, error)

MarshalWith encodes Union using any Serializer and any Keys.

func UnmarshalPtrWith

func UnmarshalPtrWith[K comparable, P Provider[K], J Keys](s Serializer, b []byte, u *Union[K, P]) error

UnmarshalPtrWith decodes Union like UnmarshalWith, but stores pointer payloads as pointers. This is useful for `oneof` patterns that rely on pointer receiver method sets.

func UnmarshalWith

func UnmarshalWith[K comparable, P Provider[K], J Keys](s Serializer, b []byte, u *Union[K, P]) error

UnmarshalWith decodes Union using any Serializer and any Keys.

Types

type BadPayloadShapeError

type BadPayloadShapeError struct {
	Kind    any
	KindKey string
	Reason  BadPayloadShapeReason
}

BadPayloadShapeError reports a structurally invalid payload shape for the selected encoding mode. KindKey is the discriminator key for the current layout (for example "kind" or "type").

func (*BadPayloadShapeError) Error

func (e *BadPayloadShapeError) Error() string

type BadPayloadShapeReason

type BadPayloadShapeReason string

BadPayloadShapeReason classifies payload shape validation failures produced by this package (currently used by inline payload encoding).

const (
	// BadPayloadShapeInlineNonObject means inline mode was requested for a payload
	// that serialized to a non-object shape (for example a scalar or array).
	BadPayloadShapeInlineNonObject BadPayloadShapeReason = "inline_non_object"
	// BadPayloadShapeInlineReservedKey means the inline payload contained the
	// discriminator key, which would overwrite the tag field.
	BadPayloadShapeInlineReservedKey BadPayloadShapeReason = "inline_reserved_key"
)

type Codec added in v1.1.0

type Codec[K comparable, P Provider[K], J Keys] struct {
	// contains filtered or unexported fields
}

Codec binds a Serializer for a specific union configuration (K/P/J) so repeated marshal/unmarshal calls do not need to pass the serializer each time.

func NewCodec added in v1.1.0

func NewCodec[K comparable, P Provider[K], J Keys](s Serializer) Codec[K, P, J]

NewCodec constructs a bound Codec for repeated operations using the same serializer and union layout.

func (Codec[K, P, J]) Marshal added in v1.1.0

func (c Codec[K, P, J]) Marshal(u Union[K, P]) ([]byte, error)

Marshal encodes Union using the Codec's bound Serializer.

func (Codec[K, P, J]) Unmarshal added in v1.1.0

func (c Codec[K, P, J]) Unmarshal(b []byte, u *Union[K, P]) error

Unmarshal decodes Union using the Codec's bound Serializer.

func (Codec[K, P, J]) UnmarshalPtr added in v1.1.0

func (c Codec[K, P, J]) UnmarshalPtr(b []byte, u *Union[K, P]) error

UnmarshalPtr decodes Union like Unmarshal, but stores pointer payloads as pointers (same semantics as UnmarshalPtrWith).

type DefaultKeys

type DefaultKeys struct{}

func (DefaultKeys) BodyKeyName

func (DefaultKeys) BodyKeyName() string

func (DefaultKeys) KindKeyName

func (DefaultKeys) KindKeyName() string

type InlineKeys

type InlineKeys struct{}

InlineKeys uses the default "kind" key with inline payload fields.

func (InlineKeys) BodyKeyName

func (InlineKeys) BodyKeyName() string

func (InlineKeys) InlinePayload

func (InlineKeys) InlinePayload() bool

func (InlineKeys) KindKeyName

func (InlineKeys) KindKeyName() string

type InlinePayloadKeys

type InlinePayloadKeys interface {
	InlinePayload() bool
}

InlinePayloadKeys is an optional extension for Keys. When implemented and InlinePayload() returns true, payload fields are encoded inline with the tag (for example {"kind":"foo","a":1}) instead of under a body key (for example {"kind":"foo","body":{"a":1}}).

BodyKeyName is ignored in inline mode but remains part of Keys for backwards compatibility.

type Keys

type Keys interface {
	KindKeyName() string
	BodyKeyName() string
}

type MissingKindError

type MissingKindError struct {
	Key string
}

MissingKindError reports that the discriminator field was not present. Use errors.As to inspect which key name was expected.

func (*MissingKindError) Error

func (e *MissingKindError) Error() string

type Provider

type Provider[K comparable] interface {
	Registry() Registry[K]
}

type Registry

type Registry[K comparable] map[K]func() any

type Serializer

type Serializer interface {
	Marshal(v any) ([]byte, error)
	Unmarshal(b []byte, v any) error
}

Serializer is the only thing you need from a codec (json/yaml/toml/cbor/etc).

type Union

type Union[K comparable, P Provider[K]] struct {
	Type  K
	Value any
}

type UnknownKindError

type UnknownKindError struct {
	Kind any
}

UnknownKindError reports that the discriminator value was decoded but not found in the provider registry. Kind preserves the decoded value.

func (*UnknownKindError) Error

func (e *UnknownKindError) Error() string

Jump to

Keyboard shortcuts

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