go-viper/mapstructure

repository·main·Indexed 19 days ago

https://github.com/go-viper/mapstructure

A Go library for decoding generic map values into native Go structures and vice versa. It provides robust error handling, support for custom struct tags (including squash and remain), and a flexible DecodeHook system for data transformations. This repository is the maintained fork of the original mitchellh/mapstructure library.

Tokens
3.7K
Snippets
16
Records
26
Agent score
63%

What's inside go-viper/mapstructure

  1. What is mapstructure and when should I use it?

    main

    The mapstructure library is used for decoding generic map values (like map[string]interface{}) into native Go structures.

    It is particularly useful when dealing with data streams (JSON, Gob, etc.) where the schema is not fully known until part of the data is read. For example, if a JSON object contains a type field that determines which struct should be used for the rest of the data, you can:

    1. Decode the initial data into a map[string]interface{}.
    2. Inspect the specific field (e.g., type).
    3. Use mapstructure to decode that map into the appropriate, specific Go struct.
  2. Migrate from `github.com/mitchellh/mapstructure` to `v2`

    main

    This repository is the maintained fork of the original mitchellh/mapstructure library. Because the API is identical, you can migrate by updating your import paths to github.com/go-viper/mapstructure/v2.

    To automate the migration, you can use this sed command to replace all occurrences in your .go files:

    sed -i 's|github.com/mitchellh/mapstructure|github.com/go-viper/mapstructure/v2|g' $(find . -type f -name '*.go')

    If you are not ready to update your imports immediately, you can use the Go modules replace directive to use the backported fixes from this repository while still using the old import path:

    replace github.com/mitchellh/mapstructure => github.com/go-viper/mapstructure v1.6.0
  3. How struct decoding handles squashing and remaining fields

    main

    When decoding into a struct, mapstructure supports two advanced behaviors via configuration and struct tags:

    1. Squashing: If a field is marked as squash (either via the Squash configuration option or a specific tag option), the decoder treats the field as an embedded struct. It will attempt to decode the keys of the input map directly into the fields of that embedded struct rather than looking for a nested map.

    2. Remaining Fields: You can capture all keys from the input map that were not matched to any specific struct field by using the remain tag on a field of type map[string]any. The decoder will collect all unused keys and their values into this map.

    3. Error Handling for Unused/Unset Fields: The decoder can be configured to return errors if there are keys in the input map that don't match any struct field (ErrorUnused) or if there are struct fields that were not populated by the input data (ErrorUnset).

  4. Implement the Unmarshaler interface for custom decoding

    main

    To provide custom decoding logic for a specific type, implement the Unmarshaler interface. The decoder checks for this interface on both value and pointer receivers.

    When the decoder encounters a type that implements Unmarshaler, it will call the implementation instead of using its default reflection-based decoding logic. The decoder is optimized to check for pointer receivers first, as they are the most common implementation pattern.

  5. Squash embedded structs

    main

    By default, embedded structs are treated as nested fields. If your input data is not nested but you want to decode it into an embedded struct, use the ,squash tag option.

    Using the ,squash tag

    type Person struct {
        Name string
    }
    
    type Friend struct {
        Person `mapstructure:",squash"` // fields from Person are treated as part of Friend
    }
    
    // Input map[string]any{"name": "alice"} will now work for Friend

    Alternatively, you can enable global squashing for all embedded structs by setting Squash: true in the DecoderConfig.

    type Person struct {
        Name string
    }
    
    type Friend struct {
        Person `mapstructure:",squash"` // fields from Person are treated as part of Friend
    }
  6. Collect unused values with the `,remain` tag

    main

    If you want to capture all keys from the source that were not mapped to specific struct fields, use the ,remain tag on a field that is a map type (e.g., map[string]any).

    type Friend struct {
        Name  string         `mapstructure:"name"` 
        Other map[string]any `mapstructure:",remain"` // captures everything else
    }
    
    // Input: map[string]any{"name": "bob", "address": "123 Maple St."}
    // Result: Name="bob", Other=map[string]any{"address": "123 Maple St."}
    type Friend struct {
        Name  string         `mapstructure:"name"` 
        Other map[string]any `mapstructure:",remain"` // captures everything else
    }
  7. Use struct tags to customize field mapping

    main

    You can control how fields are mapped using the mapstructure tag. The default tag name is mapstructure, but this can be changed in DecoderConfig.TagName.

    Renaming Fields

    To map a specific key to a field, set the tag value to the desired key name:

    type User struct {
        Username string `mapstructure:"user"` // matches key "user"
    }

    Ignoring Fields

    To prevent a field from being decoded, use a hyphen:

    type User struct {
        Password string `mapstructure:"-"`
    }
  8. Omit empty or zero values during decoding

    main

    When decoding from a struct to another type, you can use tags to skip fields based on their value:

    • ,omitempty: Omits the field if it is the zero value for its type or a zero-length element (like an empty slice).
    • ,omitzero: Omits the field if it is the zero value. Note that for slices, an empty but non-nil slice will still be encoded, whereas omitempty would omit it.
    type Source struct {
        Age  int      `mapstructure:",omitempty"` 
        URLs []string `mapstructure:",omitzero"` 
    }
  9. How weakly typed input works for slices and arrays

    main

    When WeaklyTypedInput is enabled in the decoder configuration, mapstructure provides flexible conversion logic for slices and arrays:

    • Empty Maps: An empty map in the source data will be converted into an empty slice or array in the target.
    • Single Values: If the source data is a single value (e.g., a string) and the target is a slice (e.g., []string), the decoder will "lift" the single value into a slice containing that one element.
    • Byte Slices: A string in the source data can be automatically converted into a []byte in the target.
    • Map to Slice: If the source is a map and the target is a slice, the decoder will attempt to treat the map as a slice of maps.
  10. Configure the Decoder with DecoderConfig

    main

    For advanced control, use NewDecoder(config *DecoderConfig) instead of the top-level Decode function.

    Key Configuration Options

    • WeaklyTypedInput: Enables "weak" conversions (e.g., string "1" to int 1, or string "true" to bool true).
    • ErrorUnused: If true, returns an error if the input contains keys that don't match any struct fields.
    • ErrorUnset: If true, returns an error if fields in the destination struct were not populated from the input.
    • ZeroFields: If true, clears the destination (e.g., empties a map) before decoding. If false, it merges values.
    • DecodeHook: A callback function used for custom data transformations before decoding occurs.
    • MatchName: A function to customize how map keys match struct field names (defaults to strings.EqualFold).
    • MapFieldName: A function to transform struct field names into map keys (e.g., converting PascalCase to snake_case).
  11. Compose multiple DecodeHookFuncs with ComposeDecodeHookFunc

    main

    Use ComposeDecodeHookFunc to create a single DecodeHookFunc that chains multiple hooks together. The hooks are executed in the order they are provided, and the output of one hook is passed as the input to the next. If any hook in the chain returns an error, the entire composition returns that error.

    // Example of composing hooks
    hook := mapstructure.ComposeDecodeHookFunc(
        mapstructure.StringToTimeDurationHookFunc(),
        mapstructure.StringToIPHookFunc(),
    )