dd

package
v1.0.5 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 15 Imported by: 24

README

dd - Dynamic Data

Convert between Go structs and maps with ease

The dd package provides bidirectional data binding between Go structs and map[string]any, enabling dynamic data handling for configuration, persistence, and API marshaling. Since maps are a foundational data structure, this facilitates seamless integration with any network protocol, object store, database, or file format that works with key-value data.

Quick Start

import "github.com/michaelquigley/df/dd"

// struct → map
user := User{Name: "John", Age: 30}
data, _ := dd.Unbind(user)
// data: map[string]any{"name": "John", "age": 30}

// map → struct  
userData := map[string]any{"name": "Alice", "age": 25}
user, _ := dd.New[User](userData)
// user: User{Name: "Alice", Age: 25}

Key Features

  • Bidirectional Binding: Seamlessly convert structs ↔ maps
  • Struct Tags: Control field mapping with dd tags
  • Type Coercion: Automatic type conversion (strings→numbers, etc.)
  • Typed Maps: Full support for map[K]V with any comparable key type
  • File I/O: Direct JSON/YAML file binding with NewJSONFile[T](), UnbindYAMLFile()
  • Object References: Pointer[T] type with cycle-safe linking
  • Dynamic Types: Runtime type discrimination via Dynamic interface
  • Merge-Time Defaults: Optional nested structs can provide defaults when Merge() allocates them
  • Validation: Required fields and custom validation rules
  • Nullable Fields: Per-field +nullable handling for explicit nulls
  • Strict Mode: Opt-in exact acceptance for contract data — duplicate-key/unknown-field rejection, zero coercion, +opaque subtrees
  • Deterministic Output: UnbindJSON/UnbindYAML produce byte-stable, sorted-key output
  • JSON Lines: UnbindJSONL/UnbindJSONLWriter emit one compact, newline-terminated record per call for JSONL streams

Core Functions

  • dd.New[T](data) - Type-safe struct creation from map
  • dd.Bind(target, data) - Bind data to existing struct
  • dd.Unbind(struct) - Convert struct to map
  • dd.Merge(target, data) - Overlay partial data onto an existing struct while preserving existing values

Deterministic Output

UnbindJSON, UnbindJSONL, UnbindYAML, and their writer/file variants produce deterministic output: for a given input value, the serialized bytes are identical across runs, processes, and versions. All keys — struct field names and map keys alike — are emitted in sorted order, and slice/array element order is preserved as-is. Fields captured via +extra are interleaved in sorted order with the rest, not appended at the end. This makes dd output safe to commit to version control and diff without spurious churn.

The guarantee applies to the serialized forms. The raw Unbind() return is a Go map[string]any and is therefore unordered; ordering is realized only at serialization.

Two distinct keys of one map never collapse into one serialized key. Map keys are stringified on the way out, and a map whose keys share a spelling — an interface-keyed map[any]string{1: "int", "1": "str"} — has no lossless JSON or YAML form, so Unbind refuses it with a KeyCollisionError rather than letting map iteration order decide which entry survives. Typed maps (map[int]V, map[string]V) cannot collide in ordinary use.

data, _ := dd.UnbindJSON(cfg)
// identical bytes every time, keys sorted — clean git diffs

Strict Acceptance Mode

dd's default posture is forgiving: unknown keys are ignored, duplicate JSON keys resolve last-wins inside the parser, and values coerce across types ("5" becomes an int, 5 becomes a string). Forgiving YAML retains yaml.v3's existing duplicate-key rejection. That is right for config files and local records. Strict mode is the opposite posture, for data whose exact spelling is the contract — signed payloads, hash-pinned documents, normative wire formats:

err := dd.BindJSON(&doc, data, dd.Strict())

With dd.Strict():

  • Intake (BindJSON, BindYAML, and their reader/file variants) rejects duplicate keys anywhere, trailing data after the document, YAML aliases and anchors, and YAML scalars outside the JSON value model (quote timestamps to bind them as strings). Numbers are preserved as json.Number — an authored YAML 5.00 reaches binding as "5.00", never a float.
  • Binding rejects input keys the target struct does not declare, and refuses type coercion entirely: a number arriving at a string field is an error, not a conversion. Integer fields require integer lexemes (no fractions, no exponents) and overflow is refused; native signed and unsigned Go values do not cross-bind. Map keys must have string as their underlying type; forgiving mode retains conversion into numeric and boolean map keys. time.Time accepts only its defined RFC3339 encoding; time.Duration only its duration string.
  • A field tagged +opaque (a map[string]any) accepts any members and captures its raw subtree uninterpreted — syntactic intake rules still apply inside it, binding rules do not. A field tagged +extra still captures unknown keys by declared intent. A field tagged +nullable treats an explicit null as absent in both strict and forgiving mode; when combined with +required, null is rejected as required-missing.

The strict decoders are public for pipelines that need to inspect or normalize the tree between intake and binding:

tree, err := dd.DecodeStrictYAML(data)   // duplicate-key-checked, lexeme-preserving
// ... normalize ...
err = dd.Bind(&doc, tree, dd.Strict())

Custom Converters, Dynamic binders, and UnmarshalDd implementations remain in effect under strict mode. Strict intake validates syntax before delegation, but the custom machinery owns its field and type acceptance. In particular, dd.Strict() does not make an Unmarshaler strict; do not use a permissive or legacy unmarshaler at an exact contract boundary unless it independently validates every accepted key and value. Merge does not support strict mode (partial overlay is the opposite posture by design). The forgiving default is unchanged and pinned by tests.

Common Patterns

Struct Tags for Control

type User struct {
    Name  string `dd:"+required"`           // required field
    Email string `dd:"email_address"`       // custom field name
    Token string `dd:"-"`                   // excluded from binding
    Age   int    `dd:",+omitempty"`         // omitted during Unbind when zero
    Bio   *string `dd:",+nullable"`          // explicit null binds as absent
}

Merge-Time Defaults For Optional Nested Structs

type TLSConfig struct {
    ServerName string
    MinVersion string
}

func (c *TLSConfig) ApplyDefaults() {
    c.ServerName = "localhost"
    c.MinVersion = "1.3"
}

type Config struct {
    TLS *TLSConfig
}

cfg := &Config{}
dd.Merge(cfg, map[string]any{
    "tls": map[string]any{
        "server_name": "api.example.com",
    },
})

// cfg.TLS.ServerName == "api.example.com"
// cfg.TLS.MinVersion == "1.3"

ApplyDefaults() semantics:

  • Merge() only
  • runs only when Merge() allocates a fresh struct instance
  • not called by Bind() or New()
  • not called when Merge() is updating an existing non-nil pointer
  • incoming data is bound after defaults are applied, so explicit values still win

File Persistence

// load config from JSON
config, _ := dd.NewJSONFile[AppConfig]("config.json")

// save to YAML
dd.UnbindYAMLFile(config, "config.yaml")

JSON Lines

// one compact, newline-terminated record per call; append to any io.Writer
for _, ev := range events {
    dd.UnbindJSONLWriter(ev, w)
}

// reading back needs nothing new: one BindJSON per line
scanner := bufio.NewScanner(r)
for scanner.Scan() {
    var ev Event
    dd.BindJSON(&ev, scanner.Bytes())
}

Dynamic Types

// Handle different object types at runtime
data := map[string]any{
    "type": "user",
    "name": "John",
}
obj, _ := dd.New[dd.Dynamic](data)  // Creates appropriate type

Typed Maps

// Maps with typed keys and values
type ServerConfig struct {
    Servers map[int]Server  // int keys from JSON strings
    Cache   map[string]CacheConfig
}

// JSON: {"servers": {"1": {...}, "2": {...}}}
config, _ := dd.New[ServerConfig](data)
server := config.Servers[1]  // Direct typed access

Examples

See examples/ for progressive tutorials from basic binding to advanced object references and dynamic types.


Part of the df framework - dynamic foundation for Go applications

Documentation

Index

Constants

View Source
const (
	TypeKey = "type" // discriminator key for Dynamic types
	RefKey  = "$ref" // reference key for Pointer types
)

Magic string constants for special keys to avoid typos

Variables

This section is empty.

Functions

func Bind

func Bind(target interface{}, data map[string]any, opts ...*Options) error

Bind populates the exported fields of target (a pointer to a struct) from the given data map. Keys are matched using either a struct tag `dd:"name,+required"` (where name overrides the key and the optional "+required" flag enforces presence), `dd:",+nullable"` to treat an explicit null as absent, `dd:"-"` to skip a field, or, when no tag is provided, a best-effort snake_case conversion of the field name.

Use Bind when you need to control how the prototype object is allocated. Use New when you just want to allocate a new object to bind off the heap.

supported kinds:

  • primitives: string, bool, all int/uint sizes, float32/64, time.Duration, time.Time (from RFC3339 strings with optional fractional seconds)
  • pointers to the above
  • structs and pointers to structs (recursively bound from map[string]any)
  • slices of the above (slice items are bound from []interface{})
  • maps with comparable key types and any supported value type (map keys from JSON/YAML are coerced from strings)

interface types are not supported and will return an error if encountered, except for fields of type Dynamic which are resolved using Options.DynamicBinders.

opts are optional; pass nil or omit to use defaults.

func BindJSON added in v0.3.6

func BindJSON(target interface{}, data []byte, opts ...*Options) error

BindJSON parses JSON data and binds it to the target struct.

func BindJSONFile added in v0.3.6

func BindJSONFile(target interface{}, path string, opts ...*Options) error

BindJSONFile reads JSON from the specified file path and binds it to the target struct.

func BindJSONReader added in v0.3.6

func BindJSONReader(target interface{}, r io.Reader, opts ...*Options) error

BindJSONReader reads JSON from an io.Reader and binds it to the target struct.

func BindYAML added in v0.3.6

func BindYAML(target interface{}, data []byte, opts ...*Options) error

BindYAML parses YAML data and binds it to the target struct.

func BindYAMLFile added in v0.3.6

func BindYAMLFile(target interface{}, path string, opts ...*Options) error

BindYAMLFile reads YAML from the specified file path and binds it to the target struct.

func BindYAMLReader added in v0.3.6

func BindYAMLReader(target interface{}, r io.Reader, opts ...*Options) error

BindYAMLReader reads YAML from an io.Reader and binds it to the target struct.

func DecodeStrictJSON added in v1.0.2

func DecodeStrictJSON(data []byte) (map[string]any, error)

DecodeStrictJSON parses JSON bytes into a map[string]any tree under the strict acceptance rules: duplicate member names are rejected anywhere (JSON parsers legally disagree on which duplicate wins, so one document must not have two meanings), trailing data after the document is rejected, the top level must be an object, and numbers are preserved as json.Number rather than collapsing through float64.

the returned tree is the same shape the forgiving intake produces, so it can be inspected or normalized before binding with Bind(target, m, dd.Strict()). BindJSON with the Strict option uses this decoder automatically.

func DecodeStrictYAML added in v1.0.2

func DecodeStrictYAML(data []byte) (map[string]any, error)

DecodeStrictYAML parses YAML bytes into a map[string]any tree under the strict acceptance rules: duplicate mapping keys are rejected, aliases and anchors are rejected, mapping keys must be plain strings, the document must be a single mapping, and numbers are preserved as json.Number carrying the authored lexeme where it is JSON-valid (a bare 5.00 stays "5.00").

scalars outside the JSON value model — timestamps, binary — are rejected; quote them to bind as strings. the returned tree is the same shape the forgiving intake produces. BindYAML with the Strict option uses this decoder automatically.

func Inspect

func Inspect(source interface{}, opts ...*InspectOptions) (string, error)

Inspect returns a human-readable representation of a struct's resolved state. designed for configuration debugging and validation. secret fields marked with `dd:",+secret"` are hidden unless ShowSecrets is true.

the output format is a clean, indented pseudo-data structure optimized for readability rather than parseability.

supported types: - primitives: string, bool, all int/uint sizes, float32/64, time.Duration - pointers to the above (nil pointers shown as "<nil>") - structs and pointers to structs (recursively inspected) - slices of the above (shown as numbered lists) - Dynamic interface implementations (shown with their type) - Pointer[T] references (shown with resolved state)

opts are optional; pass nil or omit to use defaults.

func Link(targets ...interface{}) error

Link resolves all pointer references in the target objects by building a registry of all Identifiable objects and then resolving Pointer fields to their target objects. objects are namespaced by their concrete type to prevent Id clashes between different types.

func Merge

func Merge(target interface{}, data map[string]any, opts ...*Options) error

Merge populates the exported fields of an existing target struct from the given data map, preserving any existing field values that are not present in the data. This allows binding partial data to pre-initialized structs with default values. If Merge has to allocate a fresh struct instance and that type implements Defaulter, ApplyDefaults is called before incoming data is bound.

uses the same field mapping rules as Bind: struct tags, snake_case conversion, etc.

supported kinds are the same as Bind.

opts are optional; pass nil or omit to use defaults.

func MergeJSON added in v0.3.6

func MergeJSON(target interface{}, data []byte, opts ...*Options) error

MergeJSON parses JSON data and merges it with the target struct.

func MergeJSONFile added in v0.3.6

func MergeJSONFile(target interface{}, path string, opts ...*Options) error

MergeJSONFile reads JSON from the specified file path and merges it with the target struct.

func MergeJSONReader added in v0.3.6

func MergeJSONReader(target interface{}, r io.Reader, opts ...*Options) error

MergeJSONReader reads JSON from an io.Reader and merges it with the target struct.

func MergeYAML added in v0.3.6

func MergeYAML(target interface{}, data []byte, opts ...*Options) error

MergeYAML parses YAML data and merges it with the target struct.

func MergeYAMLFile added in v0.3.6

func MergeYAMLFile(target interface{}, path string, opts ...*Options) error

MergeYAMLFile reads YAML from the specified file path and merges it with the target struct.

func MergeYAMLReader added in v0.3.6

func MergeYAMLReader(target interface{}, r io.Reader, opts ...*Options) error

MergeYAMLReader reads YAML from an io.Reader and merges it with the target struct.

func MustInspect

func MustInspect(source interface{}, opts ...*InspectOptions) string

MustInspect returns a human-readable representation of a struct's resolved state, panicking if an error occurs. see Inspect for full documentation.

func New

func New[T any](data map[string]any, opts ...*Options) (*T, error)

New creates and populates a new instance of type T from the given data map. Unlike Bind, which requires a pre-allocated target pointer, New automatically allocates the object and returns a pointer to the populated struct.

Use Bind instead of New when you need to control where and how the target object is instantiated. New just allocates a fresh target off the heap.

Example usage:

type Person struct {
    Name string
    Age  int
}

data := map[string]any{"name": "John", "age": 30}
person, err := New[Person](data)
if err != nil {
    // handle error
}
// person is now *Person with Name="John" and Age=30

supported kinds and field mapping rules are the same as Bind.

opts are optional; pass nil or omit to use defaults.

func NewJSON added in v0.3.6

func NewJSON[T any](data []byte, opts ...*Options) (*T, error)

NewJSON parses JSON data and returns a new instance of type T.

func NewJSONFile added in v0.3.6

func NewJSONFile[T any](path string, opts ...*Options) (*T, error)

NewJSONFile reads JSON from the specified file path and returns a new instance of type T.

func NewJSONReader added in v0.3.6

func NewJSONReader[T any](r io.Reader, opts ...*Options) (*T, error)

NewJSONReader reads JSON from an io.Reader and returns a new instance of type T.

func NewYAML added in v0.3.6

func NewYAML[T any](data []byte, opts ...*Options) (*T, error)

NewYAML parses YAML data and returns a new instance of type T.

func NewYAMLFile added in v0.3.6

func NewYAMLFile[T any](path string, opts ...*Options) (*T, error)

NewYAMLFile reads YAML from the specified file path and returns a new instance of type T.

func NewYAMLReader added in v0.3.6

func NewYAMLReader[T any](r io.Reader, opts ...*Options) (*T, error)

NewYAMLReader reads YAML from an io.Reader and returns a new instance of type T.

func Unbind

func Unbind(source interface{}, opts ...*Options) (map[string]any, error)

Unbind converts a struct (or pointer to struct) into a map[string]any honoring the same `dd` tags used by Bind: - `dd:"name"` overrides the key name - `dd:"-"` skips the field - `dd:",+omitempty"` omits the field if it has a zero value - when no tag is provided, the key defaults to snake_case of the field name

pointers to values: if nil, the key is omitted; otherwise the pointed value is emitted. slices, structs, maps, and nested pointers are handled recursively. time.Duration values are emitted as strings using Duration.String() (e.g., "30s"). time.Time values are emitted as RFC3339 strings, preserving fractional seconds when present (e.g., "2024-03-15T14:30:45.123Z"). map keys are converted to strings for JSON/YAML compatibility; two distinct keys that convert to the same string (an interface-keyed map holding both 1 and "1") are a KeyCollisionError, since such a map has no lossless serialized form. Interface fields are not supported, except for fields of type `Dynamic` (and slices of `Dynamic`), which are converted via their ToMap() method which now returns (map[string]any, error).

the returned map is a Go map and therefore unordered; deterministic, sorted-key output is a property of the serialized forms (UnbindJSON, UnbindYAML, and their writer/file variants), not of this return value. see those functions for the determinism guarantee.

opts are optional; pass nil or omit to use defaults.

func UnbindJSON added in v0.3.6

func UnbindJSON(source interface{}, opts ...*Options) ([]byte, error)

UnbindJSON converts a struct to indented JSON bytes.

output is deterministic: for a given input value the produced bytes are identical across runs, processes, and versions. all keys — struct field names and map keys alike — are emitted in sorted order (encoding/json sorts object keys lexicographically), and slice and array element order is preserved as-is. fields captured via `+extra` are interleaved in sorted order with the rest rather than appended at the end.

func UnbindJSONFile added in v0.3.6

func UnbindJSONFile(source interface{}, path string, opts ...*Options) error

UnbindJSONFile converts a struct to JSON and writes it to the specified file path. output is deterministic with sorted keys; see UnbindJSON.

func UnbindJSONL added in v1.0.4

func UnbindJSONL(source interface{}, opts ...*Options) ([]byte, error)

UnbindJSONL converts a struct to a single JSONL record: compact JSON with no interior newlines, terminated by exactly one '\n'. one call yields one line, so writing successive records to the same destination produces a JSON Lines stream. the encoding is otherwise identical to UnbindJSON — same keys, same values, same escaping — only the whitespace differs.

output is deterministic with sorted keys; see UnbindJSON. reading a stream back needs no dedicated helper: split it on '\n' and hand each line to BindJSON or NewJSON.

func UnbindJSONLWriter added in v1.0.4

func UnbindJSONLWriter(source interface{}, w io.Writer, opts ...*Options) error

UnbindJSONLWriter converts a struct to a single JSONL record and writes it to an io.Writer. call it once per record against the same writer to produce a JSON Lines stream. output is deterministic with sorted keys; see UnbindJSONL.

func UnbindJSONWriter added in v0.3.6

func UnbindJSONWriter(source interface{}, w io.Writer, opts ...*Options) error

UnbindJSONWriter converts a struct to JSON and writes it to an io.Writer. output is deterministic with sorted keys; see UnbindJSON.

func UnbindYAML added in v0.3.6

func UnbindYAML(source interface{}, opts ...*Options) ([]byte, error)

UnbindYAML converts a struct to YAML bytes.

output is deterministic: for a given input value the produced bytes are identical across runs, processes, and versions. all keys — struct field names and map keys alike — are emitted in sorted order (yaml.v3 sorts mapping keys), and slice and array element order is preserved as-is. fields captured via `+extra` are interleaved in sorted order with the rest rather than appended at the end.

func UnbindYAMLFile added in v0.3.6

func UnbindYAMLFile(source interface{}, path string, opts ...*Options) error

UnbindYAMLFile converts a struct to YAML and writes it to the specified file path. output is deterministic with sorted keys; see UnbindYAML.

func UnbindYAMLWriter added in v0.3.6

func UnbindYAMLWriter(source interface{}, w io.Writer, opts ...*Options) error

UnbindYAMLWriter converts a struct to YAML and writes it to an io.Writer. output is deterministic with sorted keys; see UnbindYAML.

Types

type BindingError

type BindingError struct {
	Path  string
	Field string
	Key   string
	Cause error
}

BindingError represents struct field binding errors

func (*BindingError) Error

func (e *BindingError) Error() string

func (*BindingError) Unwrap

func (e *BindingError) Unwrap() error

type ConversionError

type ConversionError struct {
	Path    string
	Value   string
	Type    string
	Message string
	Cause   error
}

ConversionError represents data conversion failures

func (*ConversionError) Error

func (e *ConversionError) Error() string

func (*ConversionError) Unwrap

func (e *ConversionError) Unwrap() error

type Converter

type Converter interface {
	// FromRaw converts a raw value (from the data map) to the target type.
	// the input can be any type that appears in the data map (string, int, bool, etc.).
	FromRaw(raw interface{}) (interface{}, error)

	// ToRaw converts a typed value back to a raw value for serialization.
	// the output should be a type that can be marshaled (string, int, bool, etc.).
	ToRaw(value interface{}) (interface{}, error)
}

Converter defines a bidirectional type conversion interface for custom field types. it allows users to define how their custom types should be converted to/from the raw data.

type DdTag

type DdTag struct {
	Name       string // external field name override, empty means use default
	Required   bool   // true if field is required during binding
	Secret     bool   // true if field contains sensitive data
	Skip       bool   // true if field should be skipped entirely
	MatchValue string // expected value that must match during binding, empty means no constraint
	HasMatch   bool   // true if a match constraint is specified
	Extra      bool   // true if field should capture unmatched keys
	OmitEmpty  bool   // true if field should be omitted when zero during unbinding
	Opaque     bool   // true if field captures its raw subtree uninterpreted
	Nullable   bool   // true if an explicit null should bind as an absent field
}

DdTag holds the parsed values from a `dd` struct tag.

type Defaulter added in v0.3.13

type Defaulter interface {
	ApplyDefaults()
}

Defaulter allows a type to initialize its own default values when Merge allocates a fresh instance before overlaying external data.

type DuplicateKeyError added in v1.0.2

type DuplicateKeyError struct {
	Path string
	Key  string
}

DuplicateKeyError represents a duplicate member name rejected by strict intake — parsers legally disagree on which duplicate wins, so one document must not have two meanings.

func (*DuplicateKeyError) Error added in v1.0.2

func (e *DuplicateKeyError) Error() string

type Dynamic

type Dynamic interface {
	Type() string
	ToMap() (map[string]any, error)
}

Dynamic fields can be used when the concrete type of a field is selected dynamically through the `type` data provided in the incoming `map` that will be passed to `Bind`. A polymorphic field type.

type FileError

type FileError struct {
	Path      string
	Operation string
	Cause     error
}

FileError represents file I/O operation errors

func (*FileError) Error

func (e *FileError) Error() string

func (*FileError) IsNotFound

func (e *FileError) IsNotFound() bool

IsNotFound checks if the FileError represents a file not found error.

func (*FileError) Unwrap

func (e *FileError) Unwrap() error

type FileOptions added in v0.3.14

type FileOptions struct {
	// Mode sets the file mode used when writing output. If nil, file helpers preserve an
	// existing file's mode when possible and otherwise fall back to their default mode.
	Mode *fs.FileMode
}

FileOptions configures file output behavior for file-oriented helpers.

type Identifiable

type Identifiable interface {
	GetId() string
}

Identifiable objects can participate in pointer references by providing a unique Id.

type IndexError

type IndexError struct {
	Index int
	Cause error
}

IndexError represents errors with array/slice indexing

func (*IndexError) Error

func (e *IndexError) Error() string

func (*IndexError) Unwrap

func (e *IndexError) Unwrap() error

type InspectOptions

type InspectOptions struct {
	// MaxDepth limits recursion depth to prevent infinite loops.
	MaxDepth int
	// Indent sets the indentation string (defaults to "  ").
	Indent string
	// ShowSecrets includes secret fields in output when true.
	ShowSecrets bool
}

InspectOptions configures inspection behavior.

type KeyCollisionError added in v1.0.5

type KeyCollisionError struct {
	Key  string   // the spelling the keys collide on
	Keys []string // the colliding source keys with their go types, sorted
}

KeyCollisionError represents two distinct map keys that serialize to the same string spelling during unbinding. such a map has no lossless JSON or YAML form, and which entry survived would depend on go's randomized map iteration, so unbind refuses it rather than letting one entry silently win.

func (*KeyCollisionError) Error added in v1.0.5

func (e *KeyCollisionError) Error() string

type Linker

type Linker struct {
	// contains filtered or unexported fields
}

Linker encapsulates the linking process, providing enhanced state management and advanced features.

func NewLinker

func NewLinker(opts ...LinkerOptions) *Linker

NewLinker creates a new Linker with optional options. If no options are provided, default options are used.

func (*Linker) ClearCache

func (l *Linker) ClearCache()

ClearCache clears the internal registry cache if caching is enabled.

func (l *Linker) Link(targets ...interface{}) error

Link resolves all pointer references in the target objects by building a registry of all Identifiable objects and then resolving Pointer fields to their target objects. objects are namespaced by their concrete type to prevent Id clashes between different types.

func (*Linker) Register

func (l *Linker) Register(targets ...interface{}) error

Register performs phase 1 of linking: collecting all Identifiable objects. This can be used for multi-stage linking where you want to register objects from multiple sources before resolving references.

func (*Linker) ResolveReferences

func (l *Linker) ResolveReferences(target interface{}) error

ResolveReferences performs phase 2 of linking: resolving all pointer references using the collected registry. This can be used after collecting from multiple sources.

type LinkerOptions

type LinkerOptions struct {
	// EnableCaching enables registry caching for repeated linking operations
	EnableCaching bool
	// AllowPartialResolution allows linking to succeed even if some references can't be resolved
	AllowPartialResolution bool
}

LinkerOptions configures the behavior of a Linker instance.

type Marshaler

type Marshaler interface {
	MarshalDd() (map[string]any, error)
}

Marshaler allows a type to define its own marshalling logic to a map[string]any.

type MultipleExtraFieldsError added in v0.3.8

type MultipleExtraFieldsError struct {
	Path string
}

MultipleExtraFieldsError represents the error when a struct has more than one +extra field

func (*MultipleExtraFieldsError) Error added in v0.3.8

func (e *MultipleExtraFieldsError) Error() string

type Options

type Options struct {
	// DynamicBinders maps a discriminator string (found under the "type" key in the input map) to a function that
	// consumes the full map and returns a concrete value implementing the Dynamic interface.
	DynamicBinders map[string]func(map[string]any) (Dynamic, error)

	// FieldDynamicBinders allows specifying binder sets per field path. The key is the structured path of the field as
	// used internally by Bind, e.g.: "Root.Items" for a slice field, "Root.Nested.Field" for nested fields.
	// any array indices in the path are ignored for matching purposes.
	// when present for a field, this map takes precedence over DynamicBinders.
	FieldDynamicBinders map[string]map[string]func(map[string]any) (Dynamic, error)

	// Converters maps Go types to custom converters for type conversion.
	// the key is the reflect.Type of the target field, and the value is a Converter
	// that handles bidirectional conversion between raw data and the target type.
	Converters map[reflect.Type]Converter

	// File configures file I/O behavior for helpers such as UnbindJSONFile and UnbindYAMLFile.
	File *FileOptions

	// Strict enables strict acceptance mode: intake rejects duplicate keys
	// and non-JSON scalars and preserves numbers as json.Number; binding
	// rejects unknown input keys and refuses type coercion. see Strict() for
	// the full behavior. the default (false) is dd's forgiving posture, which
	// is unchanged.
	Strict bool
}

Options configures binding behavior.

func Strict added in v1.0.2

func Strict() *Options

Strict returns Options enabling strict acceptance mode.

dd's default posture is forgiving — unknown keys are ignored, duplicate JSON keys resolve last-wins inside the parser, and values coerce across types ("5" becomes an int, 5 becomes a string). forgiving YAML retains yaml.v3's existing duplicate-key rejection. that is right for config files and local records. strict mode is the opposite posture, for data whose exact spelling is the contract (signed payloads, hash-pinned documents):

  • intake (BindJSON/BindYAML and their reader/file variants) rejects duplicate keys, trailing data, YAML aliases, and non-JSON scalars, and preserves numbers as json.Number — see DecodeStrictJSON and DecodeStrictYAML, which are also usable directly when a tree needs inspection or normalization between intake and binding
  • binding rejects input keys the target struct does not declare (unless the struct carries a +extra field, which captures them by declared intent) and refuses type coercion: a number arriving at a string field is an error, not a conversion; map keys must have string as their underlying type
  • a field tagged +opaque (a map[string]any) accepts any members and captures the raw subtree uninterpreted — syntactic intake rules still apply inside it, binding rules do not

custom Converters, Dynamic binders, and UnmarshalDd implementations remain in effect under strict mode. strict intake checks the syntax before delegation, but the custom machinery owns what it accepts; a permissive Unmarshaler should not be used at an exact contract boundary. Merge does not support strict mode.

type Pointer

type Pointer[T Identifiable] struct {
	Ref      string `dd:"$ref"`
	Resolved T      // internal resolved reference (exported for reflection)
}

Pointer represents a reference to an object of type T that implements Identifiable. During binding, the reference is stored as a string. During linking, it's resolved to the actual object.

func (*Pointer[T]) IsResolved

func (p *Pointer[T]) IsResolved() bool

IsResolved returns true if the pointer has been resolved to an actual object.

func (*Pointer[T]) Resolve

func (p *Pointer[T]) Resolve() T

Resolve returns the resolved object, or the zero value of T if not yet resolved.

type PointerError

type PointerError struct {
	Path      string
	Reference string
	Message   string
	Cause     error
}

PointerError represents pointer resolution errors

func (*PointerError) Error

func (e *PointerError) Error() string

func (*PointerError) Unwrap

func (e *PointerError) Unwrap() error

type RequiredFieldError

type RequiredFieldError struct {
	Path  string
	Field string
}

RequiredFieldError represents missing required field errors

func (*RequiredFieldError) Error

func (e *RequiredFieldError) Error() string

type TypeMismatchError

type TypeMismatchError struct {
	Path     string
	Expected string
	Actual   string
}

TypeMismatchError represents type conversion errors

func (*TypeMismatchError) Error

func (e *TypeMismatchError) Error() string

type UnbindingError

type UnbindingError struct {
	Path  string
	Field string
	Key   string
	Cause error
}

UnbindingError represents struct field unbinding errors

func (*UnbindingError) Error

func (e *UnbindingError) Error() string

func (*UnbindingError) Unwrap

func (e *UnbindingError) Unwrap() error

type UnknownFieldError added in v1.0.2

type UnknownFieldError struct {
	Path string
	Key  string
}

UnknownFieldError represents an input key the target struct does not declare, rejected under strict binding.

func (*UnknownFieldError) Error added in v1.0.2

func (e *UnknownFieldError) Error() string

type Unmarshaler

type Unmarshaler interface {
	UnmarshalDd(data map[string]any) error
}

Unmarshaler allows a type to define its own unmarshalling logic from a map[string]any.

type UnsupportedError

type UnsupportedError struct {
	Path      string
	Operation string
	Type      string
}

UnsupportedError represents unsupported operation errors

func (*UnsupportedError) Error

func (e *UnsupportedError) Error() string

type ValidationError

type ValidationError struct {
	Field   string
	Message string
}

ValidationError represents errors in input validation

func (*ValidationError) Error

func (e *ValidationError) Error() string

type ValueMismatchError

type ValueMismatchError struct {
	Path     string
	Field    string
	Expected string
	Actual   string
}

ValueMismatchError represents errors when a field value doesn't match the expected constraint

func (*ValueMismatchError) Error

func (e *ValueMismatchError) Error() string

Directories

Path Synopsis
examples
dd_04_io command
dd_11_pointers command

Jump to

Keyboard shortcuts

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