mitchellh / mapstructure

Go library for decoding generic map values into native Go structures and vice versa.
https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc
MIT License
7.93k stars 677 forks source link

time.Time Hook throws `expected a map, got 'string'` #319

Closed esenmx closed 1 year ago

esenmx commented 1 year ago

Hi,

I'm trying to convert struct to a map before json.Marshal. I see to type is fixed for map[string]interface{} but is it possible to change it to string?

package main

import (
    "encoding/json"
    "github.com/mitchellh/mapstructure"
    "log"
    "reflect"
    "time"
)

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

type Player struct {
    Person   `mapstructure:",squash"`
    Position int `mapstructure:"position" json:"position"`
}

func main() {
    p := Player{
        Person: Person{
            Name:      "Foo",
            BirthDate: time.Now(),
        },
        Position: 4,
    }
    var m map[string]any
    err := Decode(p, &m)
    log.Println(err) // 'birthDate' expected a map, got 'string'
    bs, _ := json.Marshal(m)
    log.Println(string(bs))
}

func TimeToStringHookFunc() mapstructure.DecodeHookFuncType {
    return func(f reflect.Type, t reflect.Type, data any) (any, error) {
        if f != reflect.TypeOf(&time.Time{}) {
            return data, nil
        }
        if t.Kind() != reflect.Map {
            return data, nil
        }
        return data.(*time.Time).String(), nil
    }
}

func Decode(input any, result any) error {
    decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
        Metadata:   nil,
        DecodeHook: mapstructure.ComposeDecodeHookFunc(TimeToStringHookFunc()),
        Result:     result,
    })
    if err != nil {
        return err
    }
    return decoder.Decode(input)
}