Back to godoc.org
github.com/uber-go/mapdecode

Package mapdecode

v1.0.0
Latest Go to latest

The latest major version is .

Published: Aug 29, 2017 | License: MIT | Module: github.com/uber-go/mapdecode

Overview

Package mapdecode implements a generic interface{} decoder. It allows implementing custom YAML/JSON decoding logic only once. Instead of implementing the same UnmarshalYAML and UnmarshalJSON twice, you can implement Decode once, parse the YAML/JSON input into a map[string]interface{} and decode it using this package.

var data map[string]interface{}
if err := json.Decode(&data, input); err != nil {
	log.Fatal(err)
}

var result MyStruct
if err := mapdecode.Decode(&result, data); err != nil {
	log.Fatal(err)
}

This also makes it possible to implement custom markup parsing and deserialization strategies that get decoded into a user-provided struct.

Index

Examples

func Decode

func Decode(dest, src interface{}, os ...Option) error

Decode from src into dest where dest is a pointer to the value being decoded.

Primitives are mapped as-is with pointers created or dereferenced as necessary. Maps and slices use Decode recursively for each of their items. For structs, the source must be a map[string]interface{} or map[interface{}]interface{}. Each key in the map calls Decode recursively with the field of the struct that has a name similar to the key (case insensitive match).

var item struct{ Key, Value string }
err := Decode(&item, map[string]string{"key": "some key", "Value": "some value"})

The name of the field in the map may be customized with the `mapdecode` tag. (Use the TagName option to change the name of the tag.)

var item struct {
	Key   string `mapdecode:"name"`
	Value string
}
var item struct{ Key, Value string }
err := Decode(&item, map[string]string{"name": "token", "Value": "some value"})

The destination type or any subtype may implement the Decoder interface to customize how it gets decoded.

Example

Code:

type Item struct {
	Key   string `mapdecode:"name"`
	Value string
}

var item Item
err := Decode(&item, map[string]interface{}{
	"name":  "foo",
	"value": "bar",
})

if err != nil {
	panic(err)
}

fmt.Println(item.Key, item.Value)
foo bar
Example (Decoder)

Code:

var ss StringSet

err := Decode(&ss, []interface{}{"foo", "bar", "foo", "baz"})
if err != nil {
	panic(err)
}

var items []string
for item := range ss {
	items = append(items, item)
}
sort.Strings(items)

fmt.Println(items)
[bar baz foo]

type DecodeHookFunc

type DecodeHookFunc func(from, to reflect.Type, data reflect.Value) (reflect.Value, error)

DecodeHookFunc is a hook called before decoding a value into a specific type.

type Decoder

type Decoder interface {
	// Decode receives a function that will attempt to decode the source data
	// into the given target. The argument to Into MUST be a pointer to the
	// target object.
	Decode(Into) error
}

Decoder is any type which has custom decoding logic. Types may implement Decode and rely on the given Into function to read values into a different shape, validate the result, and fill themselves with it.

For example the following lets users provide a list of strings to decode a set.

type StringSet map[string]struct{}

func (ss *StringSet) Decode(into mapdecode.Into) error {
	var items []string
	if err := into(&items); err != nil {
		return err
	}

	*ss = make(map[string]struct{})
	for _, item := range items {
		(*ss)[item] = struct{}{}
	}
	return nil
}

type FieldHookFunc

type FieldHookFunc func(dest reflect.StructField, srcData reflect.Value) (reflect.Value, error)

FieldHookFunc is a hook called while decoding a specific struct field. It receives information about the target field, and the source data.

type Into

type Into func(dest interface{}) error

Into is a function that attempts to decode the source data into the given shape.

Types that implement Decoder are provided a reference to an Into object so that they can decode a different shape, validate the result and populate themselves with the result.

var values []string
err := into(&value)
for _, value := range values {
	if value == "reserved" {
		return errors.New(`a value in the list cannot be "reserved"`)
	}
	self.Values = append(self.Values, value)
}

The function is safe to call multiple times if you need to try to decode different shapes. For example,

// Allow the user to just use the string "default" for the default
// configuration.
var name string
if err := into(&name); err == nil {
	if name == "default" {
		*self = DefaultConfiguration
		return
	}
	return fmt.Errorf("unknown name %q", name)
}

// Otherwise, the user must provide {someAttr: "value"} as the input for
// explicit configuration.
var custom struct{ SomeAttr string }
if err := into(&custom); err != nil {
	return err
}

self.SomeAttr = custom
return nil

If the destination type or any sub-type implements Decoder, that function will be called. This means that Into MUST NOT be called on the type whose Decode function is currently running or this will end up in an infinite loop.

type Option

type Option func(*options)

Option customizes the behavior of Decode.

func DecodeHook

func DecodeHook(f DecodeHookFunc) Option

DecodeHook registers a hook to be called before a value is decoded by the system.

This hook will be called with information about the target type and the source data that will fill it.

Multiple decode hooks may be specified by providing this option multiple times. Hooks are exected in-order, feeding values from one hook to the next.

func FieldHook

func FieldHook(f FieldHookFunc) Option

FieldHook registers a hook to be called when a struct field is being decoded by the system.

This hook will be called with information about the target field of a struct and the source data that will fill it.

Field hooks may return a value of the same type as the source data or the same type as the target. Other value decoding hooks will still be executed on this field.

Multiple field hooks may be specified by providing this option multiple times. Hooks are exected in-order, feeding values from one hook to the next.

func IgnoreUnused

func IgnoreUnused(ignore bool) Option

IgnoreUnused specifies whether we should ignore unused attributes in YAML. By default, decoding will fail if an unused attribute is encountered.

func TagName

func TagName(name string) Option

TagName changes the name of the struct tag under which field names are expected.

Example

Code:

var item struct {
	Value string `foo:"item"`
}

err := Decode(&item, map[string]interface{}{
	"item": "hello",
}, TagName("foo"))

if err != nil {
	panic(err)
}

fmt.Println(item.Value)
hello

func YAML

func YAML() Option

YAML may be specified to decode go-yaml (gopkg.in/yaml.v2) compatible types. Use this option if you have types that implement yaml.Unmarshaler and use the `yaml` tag.

type StringSet map[string]struct{}

func (ss *StringSet) UnmarshalYAML(decode func(interface{}) error) error {
	var items []string
	if err := decode(&items); err != nil {
		return err
	}
	// ..
}

var x StringSet
mapdecode.Decode(&x, data, mapdecode.YAML())

Caveat: None of the go-yaml flags are supported. Only the attribute name changes will be respected. Further, note that go-yaml ignores unused attributes but mapdecode fails on unused attributes by default. Use IgnoreUnused to cusotmize this behavior.

Package Files

Documentation was rendered with GOOS=linux and GOARCH=amd64.

Jump to identifier

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to identifier