Git Product home page Git Product logo

mapstructure's Introduction

mapstructure

GitHub Workflow Status go.dev reference Go Version

mapstructure is a Go library for decoding generic map values to structures and vice versa, while providing helpful error handling.

This library is most useful when decoding values from some data stream (JSON, Gob, etc.) where you don't quite know the structure of the underlying data until you read a part of it. You can therefore read a map[string]interface{} and use this library to decode it into the proper underlying native Go structure.

Installation

go get github.com/go-viper/mapstructure/v2

Migrating from github.com/mitchellh/mapstructure

@mitchehllh announced his intent to archive some of his unmaintained projects (see here and here). This is a repository achieved the "blessed fork" status.

You can migrate to this package by changing your import paths in your Go files to github.com/go-viper/mapstructure/v2. The API is the same, so you don't need to change anything else.

Here is a script that can help you with the migration:

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

If you need more time to migrate your code, that is absolutely fine.

Some of the latest fixes are backported to the v1 release branch of this package, so you can use the Go modules replace feature until you are ready to migrate:

replace github.com/mitchellh/mapstructure => github.com/go-viper/mapstructure v1.6.0

Usage & Example

For usage and examples see the documentation.

The Decode function has examples associated with it there.

But Why?!

Go offers fantastic standard libraries for decoding formats such as JSON. The standard method is to have a struct pre-created, and populate that struct from the bytes of the encoded format. This is great, but the problem is if you have configuration or an encoding that changes slightly depending on specific fields. For example, consider this JSON:

{
  "type": "person",
  "name": "Mitchell"
}

Perhaps we can't populate a specific structure without first reading the "type" field from the JSON. We could always do two passes over the decoding of the JSON (reading the "type" first, and the rest later). However, it is much simpler to just decode this into a map[string]interface{} structure, read the "type" key, then use something like this library to decode it into the proper structure.

Credits

Mapstructure was originally created by @mitchellh. This is a maintained fork of the original library.

Read more about the reasons for the fork here.

License

The project is licensed under the MIT License.

mapstructure's People

Contributors

abhinav avatar bastjan avatar bored-engineer avatar calvn avatar carlohamalainen avatar dependabot[bot] avatar dnephin avatar gernoteger avatar gyim avatar jen20 avatar julnicolas avatar kochetkov-av avatar larstore-sonat avatar m1k1o avatar mback2k avatar mcos avatar mitchellh avatar mkeeler avatar nmiyake avatar nobu-k avatar ozgursoy avatar perenecabuto avatar radeksimko avatar ryanuber avatar sagikazarmark avatar skipor avatar suzuki-shunsuke avatar tlipoca9 avatar triarius avatar uvw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mapstructure's Issues

Mapstructure tags are ignored on slice struct items

I'm trying to convert a struct to a map, and can't figure out how to make it work.

Basically I have these structs:

type SQLStepResult struct {
	Queries []QueryResult `mapstructure:"queries"`
}

type QueryResult struct {
	Rows []map[string]any `mapstructure:"rows"`
}

and I want to convert SQLStepResult to:

{
  "queries": [
    {
      "rows": [
        // ...
      ]
    }
  ]
}

But I always end up with Rows instead of rows.

I tried using the undocumented mapstructure.RecursiveStructToMapHookFunc but it doesn't appear to do anything

Is there a solution?

Go playground for reference: https://go.dev/play/p/ZlTOw1mqnH5

DecodeHook for url.URL

Hello,
I'd like to add a decode hook for parsing url.URL. Would you accept a PR for it?

Feature Request: Metadata when decoding into maps

I would like to be able to decode to a map, with deep processing of the data, and collect metadata about the keys that were decoded. For example:

actual := map[string]interface{}
config := &mapstructure.DecoderConfig{
  Metadata:   &mapstructure.Metadata{},
  Result:     actual,
}
decoder, err := mapstructure.NewDecoder(config)

decoder.Decode(map[string]interface{}{
  "a": map[string]interface{}{
    "b": "value",
  },
})
assert.Equals(t,  []string{"[a]", "[a][b]"}, config.Metadata.Keys)

At the moment, the nested map (a) is simply referred to directly in the target map, and the .Metadata.Keys contains only [a].

Allow DecodeHook to run for zero values

Use Case

In the OpenTelemetry Collector we support a configuration like this:

receiver:
  http:
  grpc:

Here the http: entry has no fields, but its presence indicate that the user wants an HTTP server, with all standard defaults. If http: is missing than this server will not be started.

Problem
Today we're handling it with a custom hook for the receiver: which catches the scenario and applies the defaults. The hook is brittle as it depends on the exact YAML field name.

We've been experimenting with a custom "Optional" type that would allow to express this much cleaner:

type ReceiverConfig struct {
  HTTP optional.Optional[HTTPServerConfig] `mapstructure:"http"`
}

func DefaultConfig() *ReceiverConfig {
  return &ReceiverConfig{
    HTTP: optional.WithDefault(defaultHTTPConfig), // passing a function
  }
}

func defaultHTTPConfig() HTTPServerConfig { ... }

Here optional.WithDefault returns Optional that has no value, but if during unmarshaling it sees that there was a corresponding entry it first creates default value and then runs normal unmarshal on it.

The issue is that this approach doesn't work today because when mapstructure sees an empty value http: it just exists, without running the decode hook (which could've made the result non-empty).

if input == nil {
// If the data is nil, then we don't set anything, unless ZeroFields is set
// to true.
if d.config.ZeroFields {
outVal.Set(reflect.Zero(outVal.Type()))
if d.config.Metadata != nil && name != "" {
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
}
}
return nil
}

Proposal

Could we add another config flag to allow not bailing early on empty values, at least to allow decoder hooks to run?

Usability of `DecodeHookFunc*` types

Hi ๐Ÿ‘‹
Thanks for taking over the project!

I've noticed in the past (and today I'm noticing again), that it's not immediately obvious to me how to use the DecodeHookFunc* family of types/ funcs. Both times, I ended up diving into the code to understand how they work from reading the tests and implementations of StringTo{stdLibType}HookFuncs.
The code is very easy to read, so this wasn't exactly a bad experience ๐Ÿ˜„ But: I'd still expect the GoDoc to contain all the info I need to use them correctly.

IMO they could really use an example and/ or a section in the package overview. Therefore, I suggest to

Would anybody object if I created a PR to do this?

Allow for multiple tags

I receive data from multiple sources and the key's of the data are not always the same.

For example, one source may provide a map similar to
{"LAT": 1, "LON": 2}

While another source may provide a map similar to
{"RLT_FOO": 1, "RLN_BAR": 2}

It would be very helpful if I could simply supply multiple tags for a single field.

For example

type Foo struct {
  Lat float64 `mapstructure:"LAT,RLT_FOO"`
  Lon float64 `mapstructure:"LON,RLN_BAR"`
}

The tags could be resolved in order and the first one that matches could be used.
This would avoid having to run a re-keying operation on maps before running through the mapstructure decoder.

I know there is the MatchName function but that only works if the variations are derivable from a base fieldName, if each field could have one of multiple unique keys it becomes a pain.

Also, if I'm just flat out missing a feature that would make this easy... please let me know.
Thanks

How to customize the values mapped to the map?

package main

import (
	"fmt"
	"reflect"
	"time"

	"github.com/go-viper/mapstructure/v2"
)

type Person struct {
	Time time.Time `json:"time"`
	Name string    `json:"name"`
}

func main() {
	result := map[string]any{}
	input := Person{time.Now(), "testName"}

	decoder, errDecoder := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		DecodeHook: func(f reflect.Type, t reflect.Type, data any) (any, error) {
			if value, ok := data.(*time.Time); ok {
				return value.Format(time.DateTime), nil
			}
			return data, nil
		},
		Result:  &result,
		TagName: "json",
	})

	if errDecoder != nil {
		panic(errDecoder)
	}

	err := decoder.Decode(input)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v\n", result)
}

Here, I want to add the 'time' of the structure Convert the Time type to a string in the 'result', but it cannot be done. report errors:

panic: 'time' expected a map, got 'string'

goroutine 1 [running]:
main.main()
	C:/Users/78081/AppData/Roaming/JetBrains/GoLand2024.1/scratches/scratch.go:37 +0x1a6

Is there a solution?

2.0 Release date?

There is currently a 2.0.0 alpha version which contains some useful fixes.

When I can expect a full 2.0.0 release, which is out of alpha?

Performance of decode hook

Hello, thanks for having taken the time of forking https://github.com/mitchellh/mapstructure/

When using the original project noticed the dynamic decode hook was an expensive part of decoding due to it using reflect on the hook on each pass. Which can happens a lot due as soon as you have slices or nested struct.

I had opened this PR mitchellh#324 to pre compute the hook to typed method.

I think it would be easy to port to the fork, is that something that would interest you ? (In which case I would be interested in helping ๐Ÿ˜„ )

Fabrice

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.