Repository: goinggo/mapstructure Branch: master Commit: 194205d9b4a9 Files: 8 Total size: 65.9 KB Directory structure: gitextract_pnuccycb/ ├── LICENSE ├── README.md ├── error.go ├── mapstructure.go ├── mapstructure_benchmark_test.go ├── mapstructure_bugs_test.go ├── mapstructure_examples_test.go └── mapstructure_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2013 Mitchell Hashimoto, William Kennedy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # mapstructure 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 Standard `go get`: ``` $ go get github.com/goinggo/mapstructure ``` ## Usage & Example For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure). The `Decode`, `DecodePath` and `DecodeSlicePath` functions have 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: ```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. ## DecodePath Sometimes you have a large and complex JSON document where you only need to decode a small part. ``` { "userContext": { "conversationCredentials": { "sessionToken": "06142010_1:75bf6a413327dd71ebe8f3f30c5a4210a9b11e93c028d6e11abfca7ff" }, "valid": true, "isPasswordExpired": false, "cobrandId": 10000004, "channelId": -1, "locale": "en_US", "tncVersion": 2, "applicationId": "17CBE222A42161A3FF450E47CF4C1A00", "cobrandConversationCredentials": { "sessionToken": "06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b" }, "preferenceInfo": { "currencyCode": "USD", "timeZone": "PST", "dateFormat": "MM/dd/yyyy", "currencyNotationType": { "currencyNotationType": "SYMBOL" }, "numberFormat": { "decimalSeparator": ".", "groupingSeparator": ",", "groupPattern": "###,##0.##" } } }, "lastLoginTime": 1375686841, "loginCount": 299, "passwordRecovered": false, "emailAddress": "johndoe@email.com", "loginName": "sptest1", "userId": 10483860, "userType": { "userTypeId": 1, "userTypeName": "normal_user" } } ``` It is nice to be able to define and pull the documents and fields you need without having to map the entire JSON structure. ``` type UserType struct { UserTypeId int UserTypeName string } type NumberFormat struct { DecimalSeparator string `jpath:"userContext.preferenceInfo.numberFormat.decimalSeparator"` GroupingSeparator string `jpath:"userContext.preferenceInfo.numberFormat.groupingSeparator"` GroupPattern string `jpath:"userContext.preferenceInfo.numberFormat.groupPattern"` } type User struct { Session string `jpath:"userContext.cobrandConversationCredentials.sessionToken"` CobrandId int `jpath:"userContext.cobrandId"` UserType UserType `jpath:"userType"` LoginName string `jpath:"loginName"` NumberFormat // This can also be a pointer to the struct (*NumberFormat) } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) var user User mapstructure.DecodePath(docMap, &user) ``` ## DecodeSlicePath Sometimes you have a slice of documents that you need to decode into a slice of structures ``` [ {"name":"bill"}, {"name":"lisa"} ] ``` Just Unmarshal your document into a slice of maps and decode the slice ``` type NameDoc struct { Name string `jpath:"name"` } sliceScript := []byte(document) var sliceMap []map[string]interface{} json.Unmarshal(sliceScript, &sliceMap) var myslice []NameDoc err := DecodeSlicePath(sliceMap, &myslice) var myslice []*NameDoc err := DecodeSlicePath(sliceMap, &myslice) ``` ## Decode Structs With Embedded Slices Sometimes you have a document with arrays ``` { "cobrandId": 10010352, "channelId": -1, "locale": "en_US", "tncVersion": 2, "people": [ { "name": "jack", "age": { "birth":10, "year":2000, "animals": [ { "barks":"yes", "tail":"yes" }, { "barks":"no", "tail":"yes" } ] } }, { "name": "jill", "age": { "birth":11, "year":2001 } } ] } ``` You can decode within those arrays ``` type Animal struct { Barks string `jpath:"barks"` } type People struct { Age int `jpath:"age.birth"` // jpath is relative to the array Animals []Animal `jpath:"age.animals"` } type Items struct { Categories []string `jpath:"categories"` Peoples []People `jpath:"people"` // Specify the location of the array } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) var items Items DecodePath(docMap, &items) ``` ================================================ FILE: error.go ================================================ package mapstructure import ( "fmt" "strings" ) // Error implements the error interface and can represents multiple // errors that occur in the course of a single decode. type Error struct { Errors []string } func (e *Error) Error() string { points := make([]string, len(e.Errors)) for i, err := range e.Errors { points[i] = fmt.Sprintf("* %s", err) } return fmt.Sprintf( "%d error(s) decoding:\n\n%s", len(e.Errors), strings.Join(points, "\n")) } func appendErrors(errors []string, err error) []string { switch e := err.(type) { case *Error: return append(errors, e.Errors...) default: return append(errors, e.Error()) } } ================================================ FILE: mapstructure.go ================================================ // The mapstructure package exposes functionality to convert an // abitrary map[string]interface{} into a native Go structure. // // The Go structure can be arbitrarily complex, containing slices, // other structs, etc. and the decoder will properly decode nested // maps and so on into the proper structures in the native Go struct. // See the examples to see what the decoder is capable of. package mapstructure import ( "errors" "fmt" "reflect" "sort" "strconv" "strings" ) type DecodeHookFunc func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) // DecoderConfig is the configuration that is used to create a new decoder // and allows customization of various aspects of decoding. type DecoderConfig struct { // DecodeHook, if set, will be called before any decoding and any // type conversion (if WeaklyTypedInput is on). This lets you modify // the values before they're set down onto the resulting struct. // // If an error is returned, the entire decode will fail with that // error. DecodeHook DecodeHookFunc // If ErrorUnused is true, then it is an error for there to exist // keys in the original map that were unused in the decoding process // (extra keys). ErrorUnused bool // If WeaklyTypedInput is true, the decoder will make the following // "weak" conversions: // // - bools to string (true = "1", false = "0") // - numbers to string (base 10) // - bools to int/uint (true = 1, false = 0) // - strings to int/uint (base implied by prefix) // - int to bool (true if value != 0) // - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F, // FALSE, false, False. Anything else is an error) // - empty array = empty map and vice versa // WeaklyTypedInput bool // Metadata is the struct that will contain extra metadata about // the decoding. If this is nil, then no metadata will be tracked. Metadata *Metadata // Result is a pointer to the struct that will contain the decoded // value. Result interface{} // The tag name that mapstructure reads for field names. This // defaults to "mapstructure" TagName string } // A Decoder takes a raw interface value and turns it into structured // data, keeping track of rich error information along the way in case // anything goes wrong. Unlike the basic top-level Decode method, you can // more finely control how the Decoder behaves using the DecoderConfig // structure. The top-level Decode method is just a convenience that sets // up the most basic Decoder. type Decoder struct { config *DecoderConfig } // Metadata contains information about decoding a structure that // is tedious or difficult to get otherwise. type Metadata struct { // Keys are the keys of the structure which were successfully decoded Keys []string // Unused is a slice of keys that were found in the raw value but // weren't decoded since there was no matching field in the result interface Unused []string } // Decode takes a map and uses reflection to convert it into the // given Go native structure. val must be a pointer to a struct. func Decode(m interface{}, rawVal interface{}) error { config := &DecoderConfig{ Metadata: nil, Result: rawVal, } decoder, err := NewDecoder(config) if err != nil { return err } return decoder.Decode(m) } // DecodePath takes a map and uses reflection to convert it into the // given Go native structure. Tags are used to specify the mapping // between fields in the map and structure func DecodePath(m map[string]interface{}, rawVal interface{}) error { config := &DecoderConfig{ Metadata: nil, Result: nil, } decoder, err := NewPathDecoder(config) if err != nil { return err } _, err = decoder.DecodePath(m, rawVal) return err } // DecodeSlicePath decodes a slice of maps against a slice of structures that // contain specified tags func DecodeSlicePath(ms []map[string]interface{}, rawSlice interface{}) error { reflectRawSlice := reflect.TypeOf(rawSlice) rawKind := reflectRawSlice.Kind() rawElement := reflectRawSlice.Elem() if (rawKind == reflect.Ptr && rawElement.Kind() != reflect.Slice) || (rawKind != reflect.Ptr && rawKind != reflect.Slice) { return fmt.Errorf("Incompatible Value, Looking For Slice : %v : %v", rawKind, rawElement.Kind()) } config := &DecoderConfig{ Metadata: nil, Result: nil, } decoder, err := NewPathDecoder(config) if err != nil { return err } // Create a slice large enough to decode all the values valSlice := reflect.MakeSlice(rawElement, len(ms), len(ms)) // Iterate over the maps and decode each one for index, m := range ms { sliceElementType := rawElement.Elem() if sliceElementType.Kind() != reflect.Ptr { // A slice of objects obj := reflect.New(rawElement.Elem()) decoder.DecodePath(m, reflect.Indirect(obj)) indexVal := valSlice.Index(index) indexVal.Set(reflect.Indirect(obj)) } else { // A slice of pointers obj := reflect.New(rawElement.Elem().Elem()) decoder.DecodePath(m, reflect.Indirect(obj)) indexVal := valSlice.Index(index) indexVal.Set(obj) } } // Set the new slice reflect.ValueOf(rawSlice).Elem().Set(valSlice) return nil } // NewDecoder returns a new decoder for the given configuration. Once // a decoder has been returned, the same configuration must not be used // again. func NewDecoder(config *DecoderConfig) (*Decoder, error) { val := reflect.ValueOf(config.Result) if val.Kind() != reflect.Ptr { return nil, errors.New("result must be a pointer") } val = val.Elem() if !val.CanAddr() { return nil, errors.New("result must be addressable (a pointer)") } if config.Metadata != nil { if config.Metadata.Keys == nil { config.Metadata.Keys = make([]string, 0) } if config.Metadata.Unused == nil { config.Metadata.Unused = make([]string, 0) } } if config.TagName == "" { config.TagName = "mapstructure" } result := &Decoder{ config: config, } return result, nil } // NewPathDecoder returns a new decoder for the given configuration. // This is used to decode path specific structures func NewPathDecoder(config *DecoderConfig) (*Decoder, error) { if config.Metadata != nil { if config.Metadata.Keys == nil { config.Metadata.Keys = make([]string, 0) } if config.Metadata.Unused == nil { config.Metadata.Unused = make([]string, 0) } } if config.TagName == "" { config.TagName = "mapstructure" } result := &Decoder{ config: config, } return result, nil } // Decode decodes the given raw interface to the target pointer specified // by the configuration. func (d *Decoder) Decode(raw interface{}) error { return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem()) } // DecodePath decodes the raw interface against the map based on the // specified tags func (d *Decoder) DecodePath(m map[string]interface{}, rawVal interface{}) (bool, error) { decoded := false var val reflect.Value reflectRawValue := reflect.ValueOf(rawVal) kind := reflectRawValue.Kind() // Looking for structs and pointers to structs switch kind { case reflect.Ptr: val = reflectRawValue.Elem() if val.Kind() != reflect.Struct { return decoded, fmt.Errorf("Incompatible Type : %v : Looking For Struct", kind) } case reflect.Struct: var ok bool val, ok = rawVal.(reflect.Value) if ok == false { return decoded, fmt.Errorf("Incompatible Type : %v : Looking For reflect.Value", kind) } default: return decoded, fmt.Errorf("Incompatible Type : %v", kind) } // Iterate over the fields in the struct for i := 0; i < val.NumField(); i++ { valueField := val.Field(i) typeField := val.Type().Field(i) tag := typeField.Tag tagValue := tag.Get("jpath") // Is this a field without a tag if tagValue == "" { if valueField.Kind() == reflect.Struct { // We have a struct that may have indivdual tags. Process separately d.DecodePath(m, valueField) continue } else if valueField.Kind() == reflect.Ptr && reflect.TypeOf(valueField).Kind() == reflect.Struct { // We have a pointer to a struct if valueField.IsNil() { // Create the object since it doesn't exist valueField.Set(reflect.New(valueField.Type().Elem())) decoded, _ = d.DecodePath(m, valueField.Elem()) if decoded == false { // If nothing was decoded for this object return the pointer to nil valueField.Set(reflect.NewAt(valueField.Type().Elem(), nil)) } continue } d.DecodePath(m, valueField.Elem()) continue } } // Use mapstructure to populate the fields keys := strings.Split(tagValue, ".") data := d.findData(m, keys) if data != nil { if valueField.Kind() == reflect.Slice { // Ignore a slice of maps - This sucks but not sure how to check if strings.Contains(valueField.Type().String(), "map[") { goto normal_decode } // We have a slice mapSlice := data.([]interface{}) if len(mapSlice) > 0 { // Test if this is a slice of more maps _, ok := mapSlice[0].(map[string]interface{}) if ok == false { goto normal_decode } // Extract the maps out and run it through DecodeSlicePath ms := make([]map[string]interface{}, len(mapSlice)) for index, m2 := range mapSlice { ms[index] = m2.(map[string]interface{}) } DecodeSlicePath(ms, valueField.Addr().Interface()) continue } } normal_decode: decoded = true err := d.decode("", data, valueField) if err != nil { return false, err } } } return decoded, nil } // Decodes an unknown data type into a specific reflection value. func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error { if data == nil { // If the data is nil, then we don't set anything. return nil } dataVal := reflect.ValueOf(data) if !dataVal.IsValid() { // If the data value is invalid, then we just set the value // to be the zero value. val.Set(reflect.Zero(val.Type())) return nil } if d.config.DecodeHook != nil { // We have a DecodeHook, so let's pre-process the data. var err error data, err = d.config.DecodeHook(d.getKind(dataVal), d.getKind(val), data) if err != nil { return err } } var err error dataKind := d.getKind(val) switch dataKind { case reflect.Bool: err = d.decodeBool(name, data, val) case reflect.Interface: err = d.decodeBasic(name, data, val) case reflect.String: err = d.decodeString(name, data, val) case reflect.Int: err = d.decodeInt(name, data, val) case reflect.Uint: err = d.decodeUint(name, data, val) case reflect.Float32: err = d.decodeFloat(name, data, val) case reflect.Struct: err = d.decodeStruct(name, data, val) case reflect.Map: err = d.decodeMap(name, data, val) case reflect.Slice: err = d.decodeSlice(name, data, val) default: // If we reached this point then we weren't able to decode it return fmt.Errorf("%s: unsupported type: %s", name, dataKind) } // If we reached here, then we successfully decoded SOMETHING, so // mark the key as used if we're tracking metadata. if d.config.Metadata != nil && name != "" { d.config.Metadata.Keys = append(d.config.Metadata.Keys, name) } return err } // findData locates the data by walking the keys down the map func (d *Decoder) findData(m map[string]interface{}, keys []string) interface{} { if len(keys) == 1 { if value, ok := m[keys[0]]; ok == true { return value } return nil } if value, ok := m[keys[0]]; ok == true { if m, ok := value.(map[string]interface{}); ok == true { return d.findData(m, keys[1:]) } } return nil } func (d *Decoder) getKind(val reflect.Value) reflect.Kind { kind := val.Kind() switch { case kind >= reflect.Int && kind <= reflect.Int64: return reflect.Int case kind >= reflect.Uint && kind <= reflect.Uint64: return reflect.Uint case kind >= reflect.Float32 && kind <= reflect.Float64: return reflect.Float32 default: return kind } } // This decodes a basic type (bool, int, string, etc.) and sets the // value to "data" of that type. func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataValType := dataVal.Type() if !dataValType.AssignableTo(val.Type()) { return fmt.Errorf( "'%s' expected type '%s', got '%s'", name, val.Type(), dataValType) } val.Set(dataVal) return nil } func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := d.getKind(dataVal) switch { case dataKind == reflect.String: val.SetString(dataVal.String()) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetString("1") } else { val.SetString("0") } case dataKind == reflect.Int && d.config.WeaklyTypedInput: val.SetString(strconv.FormatInt(dataVal.Int(), 10)) case dataKind == reflect.Uint && d.config.WeaklyTypedInput: val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := d.getKind(dataVal) switch { case dataKind == reflect.Int: val.SetInt(dataVal.Int()) case dataKind == reflect.Uint: val.SetInt(int64(dataVal.Uint())) case dataKind == reflect.Float32: val.SetInt(int64(dataVal.Float())) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetInt(1) } else { val.SetInt(0) } case dataKind == reflect.String && d.config.WeaklyTypedInput: i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) if err == nil { val.SetInt(i) } else { return fmt.Errorf("cannot parse '%s' as int: %s", name, err) } default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := d.getKind(dataVal) switch { case dataKind == reflect.Int: val.SetUint(uint64(dataVal.Int())) case dataKind == reflect.Uint: val.SetUint(dataVal.Uint()) case dataKind == reflect.Float32: val.SetUint(uint64(dataVal.Float())) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetUint(1) } else { val.SetUint(0) } case dataKind == reflect.String && d.config.WeaklyTypedInput: i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits()) if err == nil { val.SetUint(i) } else { return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) } default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := d.getKind(dataVal) switch { case dataKind == reflect.Bool: val.SetBool(dataVal.Bool()) case dataKind == reflect.Int && d.config.WeaklyTypedInput: val.SetBool(dataVal.Int() != 0) case dataKind == reflect.Uint && d.config.WeaklyTypedInput: val.SetBool(dataVal.Uint() != 0) case dataKind == reflect.Float32 && d.config.WeaklyTypedInput: val.SetBool(dataVal.Float() != 0) case dataKind == reflect.String && d.config.WeaklyTypedInput: b, err := strconv.ParseBool(dataVal.String()) if err == nil { val.SetBool(b) } else if dataVal.String() == "" { val.SetBool(false) } else { return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) } default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error { dataVal := reflect.ValueOf(data) dataKind := d.getKind(dataVal) switch { case dataKind == reflect.Int: val.SetFloat(float64(dataVal.Int())) case dataKind == reflect.Uint: val.SetFloat(float64(dataVal.Uint())) case dataKind == reflect.Float32: val.SetFloat(float64(dataVal.Float())) case dataKind == reflect.Bool && d.config.WeaklyTypedInput: if dataVal.Bool() { val.SetFloat(1) } else { val.SetFloat(0) } case dataKind == reflect.String && d.config.WeaklyTypedInput: f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits()) if err == nil { val.SetFloat(f) } else { return fmt.Errorf("cannot parse '%s' as float: %s", name, err) } default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type()) } return nil } func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { valType := val.Type() valKeyType := valType.Key() valElemType := valType.Elem() // Make a new map to hold our result mapType := reflect.MapOf(valKeyType, valElemType) valMap := reflect.MakeMap(mapType) // Check input type dataVal := reflect.Indirect(reflect.ValueOf(data)) if dataVal.Kind() != reflect.Map { // Accept empty array/slice instead of an empty map in weakly typed mode if d.config.WeaklyTypedInput && (dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) && dataVal.Len() == 0 { val.Set(valMap) return nil } else { return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) } } // Accumulate errors errors := make([]string, 0) for _, k := range dataVal.MapKeys() { fieldName := fmt.Sprintf("%s[%s]", name, k) // First decode the key into the proper type currentKey := reflect.Indirect(reflect.New(valKeyType)) if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { errors = appendErrors(errors, err) continue } // Next decode the data into the proper type v := dataVal.MapIndex(k).Interface() currentVal := reflect.Indirect(reflect.New(valElemType)) if err := d.decode(fieldName, v, currentVal); err != nil { errors = appendErrors(errors, err) continue } valMap.SetMapIndex(currentKey, currentVal) } // Set the built up map to the value val.Set(valMap) // If we had errors, return those if len(errors) > 0 { return &Error{errors} } return nil } func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() valType := val.Type() valElemType := valType.Elem() // Make a new slice to hold our result, same size as the original data. sliceType := reflect.SliceOf(valElemType) valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) // Check input type if dataValKind != reflect.Array && dataValKind != reflect.Slice { // Accept empty map instead of array/slice in weakly typed mode if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 { val.Set(valSlice) return nil } else { return fmt.Errorf( "'%s': source data must be an array or slice, got %s", name, dataValKind) } } // Accumulate any errors errors := make([]string, 0) for i := 0; i < dataVal.Len(); i++ { currentData := dataVal.Index(i).Interface() currentField := valSlice.Index(i) fieldName := fmt.Sprintf("%s[%d]", name, i) if err := d.decode(fieldName, currentData, currentField); err != nil { errors = appendErrors(errors, err) } } // Finally, set the value to the slice we built up val.Set(valSlice) // If there were errors, we return those if len(errors) > 0 { return &Error{errors} } return nil } func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataValKind := dataVal.Kind() if dataValKind != reflect.Map { return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind) } dataValType := dataVal.Type() if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { return fmt.Errorf( "'%s' needs a map with string keys, has '%s' keys", name, dataValType.Key().Kind()) } dataValKeys := make(map[reflect.Value]struct{}) dataValKeysUnused := make(map[interface{}]struct{}) for _, dataValKey := range dataVal.MapKeys() { dataValKeys[dataValKey] = struct{}{} dataValKeysUnused[dataValKey.Interface()] = struct{}{} } errors := make([]string, 0) // This slice will keep track of all the structs we'll be decoding. // There can be more than one struct if there are embedded structs // that are squashed. structs := make([]reflect.Value, 1, 5) structs[0] = val // Compile the list of all the fields that we're going to be decoding // from all the structs. fields := make(map[*reflect.StructField]reflect.Value) for len(structs) > 0 { structVal := structs[0] structs = structs[1:] structType := structVal.Type() for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) if fieldType.Anonymous { fieldKind := fieldType.Type.Kind() if fieldKind != reflect.Struct { errors = appendErrors(errors, fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind)) continue } // We have an embedded field. We "squash" the fields down // if specified in the tag. squash := false tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") for _, tag := range tagParts[1:] { if tag == "squash" { squash = true break } } if squash { structs = append(structs, val.FieldByName(fieldType.Name)) continue } } // Normal struct field, store it away fields[&fieldType] = structVal.Field(i) } } for fieldType, field := range fields { fieldName := fieldType.Name tagValue := fieldType.Tag.Get(d.config.TagName) tagValue = strings.SplitN(tagValue, ",", 2)[0] if tagValue != "" { fieldName = tagValue } rawMapKey := reflect.ValueOf(fieldName) rawMapVal := dataVal.MapIndex(rawMapKey) if !rawMapVal.IsValid() { // Do a slower search by iterating over each key and // doing case-insensitive search. for dataValKey, _ := range dataValKeys { mK, ok := dataValKey.Interface().(string) if !ok { // Not a string key continue } if strings.EqualFold(mK, fieldName) { rawMapKey = dataValKey rawMapVal = dataVal.MapIndex(dataValKey) break } } if !rawMapVal.IsValid() { // There was no matching key in the map for the value in // the struct. Just ignore. continue } } // Delete the key we're using from the unused map so we stop tracking delete(dataValKeysUnused, rawMapKey.Interface()) if !field.IsValid() { // This should never happen panic("field is not valid") } // If we can't set the field, then it is unexported or something, // and we just continue onwards. if !field.CanSet() { continue } // If the name is empty string, then we're at the root, and we // don't dot-join the fields. if name != "" { fieldName = fmt.Sprintf("%s.%s", name, fieldName) } if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil { errors = appendErrors(errors, err) } } if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { keys := make([]string, 0, len(dataValKeysUnused)) for rawKey, _ := range dataValKeysUnused { keys = append(keys, rawKey.(string)) } sort.Strings(keys) err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", ")) errors = appendErrors(errors, err) } if len(errors) > 0 { return &Error{errors} } // Add the unused keys to the list of unused keys if we're tracking metadata if d.config.Metadata != nil { for rawKey, _ := range dataValKeysUnused { key := rawKey.(string) if name != "" { key = fmt.Sprintf("%s.%s", name, key) } d.config.Metadata.Unused = append(d.config.Metadata.Unused, key) } } return nil } ================================================ FILE: mapstructure_benchmark_test.go ================================================ package mapstructure import ( "testing" ) func Benchmark_Decode(b *testing.B) { type Person struct { Name string Age int Emails []string Extra map[string]string } input := map[string]interface{}{ "name": "Mitchell", "age": 91, "emails": []string{"one", "two", "three"}, "extra": map[string]string{ "twitter": "mitchellh", }, } var result Person for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeBasic(b *testing.B) { input := map[string]interface{}{ "vstring": "foo", "vint": 42, "Vuint": 42, "vbool": true, "Vfloat": 42.42, "vsilent": true, "vdata": 42, } var result Basic for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeEmbedded(b *testing.B) { input := map[string]interface{}{ "vstring": "foo", "Basic": map[string]interface{}{ "vstring": "innerfoo", }, "vunique": "bar", } var result Embedded for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeTypeConversion(b *testing.B) { input := map[string]interface{}{ "IntToFloat": 42, "IntToUint": 42, "IntToBool": 1, "IntToString": 42, "UintToInt": 42, "UintToFloat": 42, "UintToBool": 42, "UintToString": 42, "BoolToInt": true, "BoolToUint": true, "BoolToFloat": true, "BoolToString": true, "FloatToInt": 42.42, "FloatToUint": 42.42, "FloatToBool": 42.42, "FloatToString": 42.42, "StringToInt": "42", "StringToUint": "42", "StringToBool": "1", "StringToFloat": "42.42", "SliceToMap": []interface{}{}, "MapToSlice": map[string]interface{}{}, } var resultStrict TypeConversionResult for i := 0; i < b.N; i++ { Decode(input, &resultStrict) } } func Benchmark_DecodeMap(b *testing.B) { input := map[string]interface{}{ "vfoo": "foo", "vother": map[interface{}]interface{}{ "foo": "foo", "bar": "bar", }, } var result Map for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeMapOfStruct(b *testing.B) { input := map[string]interface{}{ "value": map[string]interface{}{ "foo": map[string]string{"vstring": "one"}, "bar": map[string]string{"vstring": "two"}, }, } var result MapOfStruct for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeSlice(b *testing.B) { input := map[string]interface{}{ "vfoo": "foo", "vbar": []string{"foo", "bar", "baz"}, } var result Slice for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeSliceOfStruct(b *testing.B) { input := map[string]interface{}{ "value": []map[string]interface{}{ {"vstring": "one"}, {"vstring": "two"}, }, } var result SliceOfStruct for i := 0; i < b.N; i++ { Decode(input, &result) } } func Benchmark_DecodeWeaklyTypedInput(b *testing.B) { type Person struct { Name string Age int Emails []string } // This input can come from anywhere, but typically comes from // something like decoding JSON, generated by a weakly typed language // such as PHP. input := map[string]interface{}{ "name": 123, // number => string "age": "42", // string => number "emails": map[string]interface{}{}, // empty map => empty array } var result Person config := &DecoderConfig{ WeaklyTypedInput: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } for i := 0; i < b.N; i++ { decoder.Decode(input) } } func Benchmark_DecodeMetadata(b *testing.B) { type Person struct { Name string Age int } input := map[string]interface{}{ "name": "Mitchell", "age": 91, "email": "foo@bar.com", } var md Metadata var result Person config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } for i := 0; i < b.N; i++ { decoder.Decode(input) } } func Benchmark_DecodeMetadataEmbedded(b *testing.B) { input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var md Metadata var result EmbeddedSquash config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { b.Fatalf("err: %s", err) } for i := 0; i < b.N; i++ { decoder.Decode(input) } } func Benchmark_DecodeTagged(b *testing.B) { input := map[string]interface{}{ "foo": "bar", "bar": "value", } var result Tagged for i := 0; i < b.N; i++ { Decode(input, &result) } } ================================================ FILE: mapstructure_bugs_test.go ================================================ package mapstructure import "testing" // GH-1 func TestDecode_NilValue(t *testing.T) { input := map[string]interface{}{ "vfoo": nil, "vother": nil, } var result Map err := Decode(input, &result) if err != nil { t.Fatalf("should not error: %s", err) } if result.Vfoo != "" { t.Fatalf("value should be default: %s", result.Vfoo) } if result.Vother != nil { t.Fatalf("Vother should be nil: %s", result.Vother) } } // GH-10 func TestDecode_mapInterfaceInterface(t *testing.T) { input := map[interface{}]interface{}{ "vfoo": nil, "vother": nil, } var result Map err := Decode(input, &result) if err != nil { t.Fatalf("should not error: %s", err) } if result.Vfoo != "" { t.Fatalf("value should be default: %s", result.Vfoo) } if result.Vother != nil { t.Fatalf("Vother should be nil: %s", result.Vother) } } ================================================ FILE: mapstructure_examples_test.go ================================================ package mapstructure import ( "encoding/json" "fmt" ) func ExampleDecode() { type Person struct { Name string Age int Emails []string Extra map[string]string } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ "name": "Mitchell", "age": 91, "emails": []string{"one", "two", "three"}, "extra": map[string]string{ "twitter": "mitchellh", }, } var result Person err := Decode(input, &result) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: // mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}} } func ExampleDecode_errors() { type Person struct { Name string Age int Emails []string Extra map[string]string } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ "name": 123, "age": "bad value", "emails": []int{1, 2, 3}, } var result Person err := Decode(input, &result) if err == nil { panic("should have an error") } fmt.Println(err.Error()) // Output: // 5 error(s) decoding: // // * 'Name' expected type 'string', got unconvertible type 'int' // * 'Age' expected type 'int', got unconvertible type 'string' // * 'Emails[0]' expected type 'string', got unconvertible type 'int' // * 'Emails[1]' expected type 'string', got unconvertible type 'int' // * 'Emails[2]' expected type 'string', got unconvertible type 'int' } func ExampleDecode_metadata() { type Person struct { Name string Age int } // This input can come from anywhere, but typically comes from // something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{ "name": "Mitchell", "age": 91, "email": "foo@bar.com", } // For metadata, we make a more advanced DecoderConfig so we can // more finely configure the decoder that is used. In this case, we // just tell the decoder we want to track metadata. var md Metadata var result Person config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } if err := decoder.Decode(input); err != nil { panic(err) } fmt.Printf("Unused keys: %#v", md.Unused) // Output: // Unused keys: []string{"email"} } func ExampleDecode_weaklyTypedInput() { type Person struct { Name string Age int Emails []string } // This input can come from anywhere, but typically comes from // something like decoding JSON, generated by a weakly typed language // such as PHP. input := map[string]interface{}{ "name": 123, // number => string "age": "42", // string => number "emails": map[string]interface{}{}, // empty map => empty array } var result Person config := &DecoderConfig{ WeaklyTypedInput: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { panic(err) } err = decoder.Decode(input) if err != nil { panic(err) } fmt.Printf("%#v", result) // Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}} } func ExampleDecodePath() { var document string = `{ "userContext": { "conversationCredentials": { "sessionToken": "06142010_1:75bf6a413327dd71ebe8f3f30c5a4210a9b11e93c028d6e11abfca7ff" }, "valid": true, "isPasswordExpired": false, "cobrandId": 10000004, "channelId": -1, "locale": "en_US", "tncVersion": 2, "applicationId": "17CBE222A42161A3FF450E47CF4C1A00", "cobrandConversationCredentials": { "sessionToken": "06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b" }, "preferenceInfo": { "currencyCode": "USD", "timeZone": "PST", "dateFormat": "MM/dd/yyyy", "currencyNotationType": { "currencyNotationType": "SYMBOL" }, "numberFormat": { "decimalSeparator": ".", "groupingSeparator": ",", "groupPattern": "###,##0.##" } } }, "lastLoginTime": 1375686841, "loginCount": 299, "passwordRecovered": false, "emailAddress": "johndoe@email.com", "loginName": "sptest1", "userId": 10483860, "userType": { "userTypeId": 1, "userTypeName": "normal_user" } }` type UserType struct { UserTypeId int UserTypeName string } type NumberFormat struct { DecimalSeparator string `jpath:"userContext.preferenceInfo.numberFormat.decimalSeparator"` GroupingSeparator string `jpath:"userContext.preferenceInfo.numberFormat.groupingSeparator"` GroupPattern string `jpath:"userContext.preferenceInfo.numberFormat.groupPattern"` } type User struct { Session string `jpath:"userContext.cobrandConversationCredentials.sessionToken"` CobrandId int `jpath:"userContext.cobrandId"` UserType UserType `jpath:"userType"` LoginName string `jpath:"loginName"` NumberFormat // This can also be a pointer to the struct (*NumberFormat) } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) var user User DecodePath(docMap, &user) fmt.Printf("%#v", user) // Output: // mapstructure.User{Session:"06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b", CobrandId:10000004, UserType:mapstructure.UserType{UserTypeId:1, UserTypeName:"normal_user"}, LoginName:"sptest1", NumberFormat:mapstructure.NumberFormat{DecimalSeparator:".", GroupingSeparator:",", GroupPattern:"###,##0.##"}} } func ExampleDecodeSlicePath() { var document = `[{"name":"bill"},{"name":"lisa"}]` type NameDoc struct { Name string `jpath:"name"` } sliceScript := []byte(document) var sliceMap []map[string]interface{} json.Unmarshal(sliceScript, &sliceMap) var myslice []NameDoc DecodeSlicePath(sliceMap, &myslice) fmt.Printf("%#v", myslice) // Output: // []mapstructure.NameDoc{mapstructure.NameDoc{Name:"bill"}, mapstructure.NameDoc{Name:"lisa"}} } func ExampleDecodeWithEmbeddedSlice() { var document string = `{ "cobrandId": 10010352, "channelId": -1, "locale": "en_US", "tncVersion": 2, "categories":["rabbit","bunny","frog"], "people": [ { "name": "jack", "age": { "birth":10, "year":2000, "animals": [ { "barks":"yes", "tail":"yes" }, { "barks":"no", "tail":"yes" } ] } }, { "name": "jill", "age": { "birth":11, "year":2001 } } ] }` type Animal struct { Barks string `jpath:"barks"` } type People struct { Age int `jpath:"age.birth"` // jpath is relative to the array Animals []Animal `jpath:"age.animals"` } type Items struct { Categories []string `jpath:"categories"` Peoples []People `jpath:"people"` // Specify the location of the array } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) var items Items DecodePath(docMap, &items) fmt.Printf("%#v", items) // Output: // mapstructure.Items{Categories:[]string{"rabbit", "bunny", "frog"}, Peoples:[]mapstructure.People{mapstructure.People{Age:10, Animals:[]mapstructure.Animal{mapstructure.Animal{Barks:"yes"}, mapstructure.Animal{Barks:"no"}}}, mapstructure.People{Age:11, Animals:[]mapstructure.Animal(nil)}}} } func ExampleDecodeWithAbstractField() { var document = `{"Error":[{"errorDetail":"Invalid Cobrand Credentials"}]}` type YodleeError struct { Error []map[string]interface{} `jpath:"Error"` } type CobrandContext struct { YodleeError } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) var cobrandContext CobrandContext DecodePath(docMap, &cobrandContext) fmt.Printf("%#v", cobrandContext) // Output: // mapstructure.CobrandContext{YodleeError:mapstructure.YodleeError{Error:[]map[string]interface {}{map[string]interface {}{"errorDetail":"Invalid Cobrand Credentials"}}}} } ================================================ FILE: mapstructure_test.go ================================================ package mapstructure import ( "encoding/json" "reflect" "sort" "testing" ) type Basic struct { Vstring string Vint int Vuint uint Vbool bool Vfloat float64 Vextra string vsilent bool Vdata interface{} } type Embedded struct { Basic Vunique string } type EmbeddedPointer struct { *Basic Vunique string } type EmbeddedSquash struct { Basic `mapstructure:",squash"` Vunique string } type Map struct { Vfoo string Vother map[string]string } type MapOfStruct struct { Value map[string]Basic } type Nested struct { Vfoo string Vbar Basic } type Slice struct { Vfoo string Vbar []string } type SliceOfStruct struct { Value []Basic } type Tagged struct { Extra string `mapstructure:"bar,what,what"` Value string `mapstructure:"foo"` } type TypeConversionResult struct { IntToFloat float32 IntToUint uint IntToBool bool IntToString string UintToInt int UintToFloat float32 UintToBool bool UintToString string BoolToInt int BoolToUint uint BoolToFloat float32 BoolToString string FloatToInt int FloatToUint uint FloatToBool bool FloatToString string StringToInt int StringToUint uint StringToBool bool StringToFloat float32 SliceToMap map[string]interface{} MapToSlice []interface{} } func TestBasicTypes(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vint": 42, "Vuint": 42, "vbool": true, "Vfloat": 42.42, "vsilent": true, "vdata": 42, } var result Basic err := Decode(input, &result) if err != nil { t.Errorf("got an err: %s", err.Error()) t.FailNow() } if result.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vstring) } if result.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vint) } if result.Vuint != 42 { t.Errorf("vuint value should be 42: %#v", result.Vuint) } if result.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbool) } if result.Vfloat != 42.42 { t.Errorf("vfloat value should be 42.42: %#v", result.Vfloat) } if result.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vextra) } if result.vsilent != false { t.Error("vsilent should not be set, it is unexported") } if result.Vdata != 42 { t.Error("vdata should be valid") } } func TestBasic_IntWithFloat(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vint": float64(42), } var result Basic err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } } func TestDecode_Embedded(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "Basic": map[string]interface{}{ "vstring": "innerfoo", }, "vunique": "bar", } var result Embedded err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vstring != "innerfoo" { t.Errorf("vstring value should be 'innerfoo': %#v", result.Vstring) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecode_EmbeddedPointer(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "Basic": map[string]interface{}{ "vstring": "innerfoo", }, "vunique": "bar", } var result EmbeddedPointer err := Decode(input, &result) if err == nil { t.Fatal("should get error") } } func TestDecode_EmbeddedSquash(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var result EmbeddedSquash err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vstring) } if result.Vunique != "bar" { t.Errorf("vunique value should be 'bar': %#v", result.Vunique) } } func TestDecode_DecodeHook(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vint": "WHAT", } decodeHook := func(from reflect.Kind, to reflect.Kind, v interface{}) (interface{}, error) { if from == reflect.String && to != reflect.String { return 5, nil } return v, nil } var result Basic config := &DecoderConfig{ DecodeHook: decodeHook, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if result.Vint != 5 { t.Errorf("vint should be 5: %#v", result.Vint) } } func TestDecode_Nil(t *testing.T) { t.Parallel() var input interface{} = nil result := Basic{ Vstring: "foo", } err := Decode(input, &result) if err != nil { t.Fatalf("err: %s", err) } if result.Vstring != "foo" { t.Fatalf("bad: %#v", result.Vstring) } } func TestDecode_NonStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "bar", "bar": "baz", } var result map[string]string err := Decode(input, &result) if err != nil { t.Fatalf("err: %s", err) } if result["foo"] != "bar" { t.Fatal("foo is not bar") } } func TestDecode_TypeConversion(t *testing.T) { input := map[string]interface{}{ "IntToFloat": 42, "IntToUint": 42, "IntToBool": 1, "IntToString": 42, "UintToInt": 42, "UintToFloat": 42, "UintToBool": 42, "UintToString": 42, "BoolToInt": true, "BoolToUint": true, "BoolToFloat": true, "BoolToString": true, "FloatToInt": 42.42, "FloatToUint": 42.42, "FloatToBool": 42.42, "FloatToString": 42.42, "StringToInt": "42", "StringToUint": "42", "StringToBool": "1", "StringToFloat": "42.42", "SliceToMap": []interface{}{}, "MapToSlice": map[string]interface{}{}, } expectedResultStrict := TypeConversionResult{ IntToFloat: 42.0, IntToUint: 42, UintToInt: 42, UintToFloat: 42, BoolToInt: 0, BoolToUint: 0, BoolToFloat: 0, FloatToInt: 42, FloatToUint: 42, } expectedResultWeak := TypeConversionResult{ IntToFloat: 42.0, IntToUint: 42, IntToBool: true, IntToString: "42", UintToInt: 42, UintToFloat: 42, UintToBool: true, UintToString: "42", BoolToInt: 1, BoolToUint: 1, BoolToFloat: 1, BoolToString: "1", FloatToInt: 42, FloatToUint: 42, FloatToBool: true, FloatToString: "42.42", StringToInt: 42, StringToUint: 42, StringToBool: true, StringToFloat: 42.42, SliceToMap: map[string]interface{}{}, MapToSlice: []interface{}{}, } // Test strict type conversion var resultStrict TypeConversionResult err := Decode(input, &resultStrict) if err == nil { t.Errorf("should return an error") } if !reflect.DeepEqual(resultStrict, expectedResultStrict) { t.Errorf("expected %v, got: %v", expectedResultStrict, resultStrict) } // Test weak type conversion var decoder *Decoder var resultWeak TypeConversionResult config := &DecoderConfig{ WeaklyTypedInput: true, Result: &resultWeak, } decoder, err = NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("got an err: %s", err) } if !reflect.DeepEqual(resultWeak, expectedResultWeak) { t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak) } } func TestDecoder_ErrorUnused(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "hello", "foo": "bar", } var result Basic config := &DecoderConfig{ ErrorUnused: true, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err == nil { t.Fatal("expected error") } } func TestMap(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vother": map[interface{}]interface{}{ "foo": "foo", "bar": "bar", }, } var result Map err := Decode(input, &result) if err != nil { t.Fatalf("got an error: %s", err) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vother == nil { t.Fatal("vother should not be nil") } if len(result.Vother) != 2 { t.Error("vother should have two items") } if result.Vother["foo"] != "foo" { t.Errorf("'foo' key should be foo, got: %#v", result.Vother["foo"]) } if result.Vother["bar"] != "bar" { t.Errorf("'bar' key should be bar, got: %#v", result.Vother["bar"]) } } func TestMapOfStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "value": map[string]interface{}{ "foo": map[string]string{"vstring": "one"}, "bar": map[string]string{"vstring": "two"}, }, } var result MapOfStruct err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err) } if result.Value == nil { t.Fatal("value should not be nil") } if len(result.Value) != 2 { t.Error("value should have two items") } if result.Value["foo"].Vstring != "one" { t.Errorf("foo value should be 'one', got: %s", result.Value["foo"].Vstring) } if result.Value["bar"].Vstring != "two" { t.Errorf("bar value should be 'two', got: %s", result.Value["bar"].Vstring) } } func TestNestedType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } var result Nested err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } } func TestNestedTypePointer(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": &map[string]interface{}{ "vstring": "foo", "vint": 42, "vbool": true, }, } var result Nested err := Decode(input, &result) if err != nil { t.Fatalf("got an err: %s", err.Error()) } if result.Vfoo != "foo" { t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo) } if result.Vbar.Vstring != "foo" { t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring) } if result.Vbar.Vint != 42 { t.Errorf("vint value should be 42: %#v", result.Vbar.Vint) } if result.Vbar.Vbool != true { t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool) } if result.Vbar.Vextra != "" { t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra) } } func TestSlice(t *testing.T) { t.Parallel() inputStringSlice := map[string]interface{}{ "vfoo": "foo", "vbar": []string{"foo", "bar", "baz"}, } inputStringSlicePointer := map[string]interface{}{ "vfoo": "foo", "vbar": &[]string{"foo", "bar", "baz"}, } outputStringSlice := &Slice{ "foo", []string{"foo", "bar", "baz"}, } testSliceInput(t, inputStringSlice, outputStringSlice) testSliceInput(t, inputStringSlicePointer, outputStringSlice) } func TestSliceOfStruct(t *testing.T) { t.Parallel() input := map[string]interface{}{ "value": []map[string]interface{}{ {"vstring": "one"}, {"vstring": "two"}, }, } var result SliceOfStruct err := Decode(input, &result) if err != nil { t.Fatalf("got unexpected error: %s", err) } if len(result.Value) != 2 { t.Fatalf("expected two values, got %d", len(result.Value)) } if result.Value[0].Vstring != "one" { t.Errorf("first value should be 'one', got: %s", result.Value[0].Vstring) } if result.Value[1].Vstring != "two" { t.Errorf("second value should be 'two', got: %s", result.Value[1].Vstring) } } func TestInvalidType(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": 42, } var result Basic err := Decode(input, &result) if err == nil { t.Fatal("error should exist") } derr, ok := err.(*Error) if !ok { t.Fatalf("error should be kind of Error, instead: %#v", err) } if derr.Errors[0] != "'Vstring' expected type 'string', got unconvertible type 'int'" { t.Errorf("got unexpected error: %s", err) } } func TestMetadata(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vfoo": "foo", "vbar": map[string]interface{}{ "vstring": "foo", "Vuint": 42, "foo": "bar", }, "bar": "nil", } var md Metadata var result Nested config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err.Error()) } expectedKeys := []string{"Vfoo", "Vbar.Vstring", "Vbar.Vuint", "Vbar"} if !reflect.DeepEqual(md.Keys, expectedKeys) { t.Fatalf("bad keys: %#v", md.Keys) } expectedUnused := []string{"Vbar.foo", "bar"} if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestMetadata_Embedded(t *testing.T) { t.Parallel() input := map[string]interface{}{ "vstring": "foo", "vunique": "bar", } var md Metadata var result EmbeddedSquash config := &DecoderConfig{ Metadata: &md, Result: &result, } decoder, err := NewDecoder(config) if err != nil { t.Fatalf("err: %s", err) } err = decoder.Decode(input) if err != nil { t.Fatalf("err: %s", err.Error()) } expectedKeys := []string{"Vstring", "Vunique"} sort.Strings(md.Keys) if !reflect.DeepEqual(md.Keys, expectedKeys) { t.Fatalf("bad keys: %#v", md.Keys) } expectedUnused := []string{} if !reflect.DeepEqual(md.Unused, expectedUnused) { t.Fatalf("bad unused: %#v", md.Unused) } } func TestNonPtrValue(t *testing.T) { t.Parallel() err := Decode(map[string]interface{}{}, Basic{}) if err == nil { t.Fatal("error should exist") } if err.Error() != "result must be a pointer" { t.Errorf("got unexpected error: %s", err) } } func TestTagged(t *testing.T) { t.Parallel() input := map[string]interface{}{ "foo": "bar", "bar": "value", } var result Tagged err := Decode(input, &result) if err != nil { t.Fatalf("unexpected error: %s", err) } if result.Value != "bar" { t.Errorf("value should be 'bar', got: %#v", result.Value) } if result.Extra != "value" { t.Errorf("extra should be 'value', got: %#v", result.Extra) } } func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) { var result Slice err := Decode(input, &result) if err != nil { t.Fatalf("got error: %s", err) } if result.Vfoo != expected.Vfoo { t.Errorf("Vfoo expected '%s', got '%s'", expected.Vfoo, result.Vfoo) } if result.Vbar == nil { t.Fatalf("Vbar a slice, got '%#v'", result.Vbar) } if len(result.Vbar) != len(expected.Vbar) { t.Errorf("Vbar length should be %d, got %d", len(expected.Vbar), len(result.Vbar)) } for i, v := range result.Vbar { if v != expected.Vbar[i] { t.Errorf( "Vbar[%d] should be '%#v', got '%#v'", i, expected.Vbar[i], v) } } } func TestDecodePath(t *testing.T) { var document string = `{ "userContext": { "cobrandId": 10000004, "channelId": -1, "locale": "en_US", "tncVersion": 2, "applicationId": "17CBE222A42161A3FF450E47CF4C1A00", "cobrandConversationCredentials": { "sessionToken": "06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b" }, "preferenceInfo": { "currencyCode": "USD", "timeZone": "PST", "dateFormat": "MM/dd/yyyy", "currencyNotationType": { "currencyNotationType": "SYMBOL" }, "numberFormat": { "decimalSeparator": ".", "groupingSeparator": ",", "groupPattern": "###,##0.##" } } }, "loginName": "sptest1", "userId": 10483860, "userType": { "userTypeId": 1, "userTypeName": "normal_user" } }` type UserType struct { UserTypeId int UserTypeName string } type NumberFormat struct { DecimalSeparator string `jpath:"userContext.preferenceInfo.numberFormat.decimalSeparator"` GroupingSeparator string `jpath:"userContext.preferenceInfo.numberFormat.groupingSeparator"` GroupPattern string `jpath:"userContext.preferenceInfo.numberFormat.groupPattern"` } type User struct { Session string `jpath:"userContext.cobrandConversationCredentials.sessionToken"` CobrandId int `jpath:"userContext.cobrandId"` UserType UserType `jpath:"userType"` LoginName string `jpath:"loginName"` *NumberFormat } docScript := []byte(document) var docMap map[string]interface{} err := json.Unmarshal(docScript, &docMap) if err != nil { t.Fatalf("Unable To Unmarshal Test Document, %s", err) } var user User DecodePath(docMap, &user) session := "06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b" if user.Session != session { t.Errorf("user.Session should be '%s', we got '%s'", session, user.Session) } cobrandId := 10000004 if user.CobrandId != cobrandId { t.Errorf("user.CobrandId should be '%d', we got '%d'", cobrandId, user.CobrandId) } loginName := "sptest1" if user.LoginName != loginName { t.Errorf("user.LoginName should be '%s', we got '%s'", loginName, user.LoginName) } userTypeId := 1 if user.UserType.UserTypeId != userTypeId { t.Errorf("user.UserType.UserTypeId should be '%d', we got '%d'", userTypeId, user.UserType.UserTypeId) } userTypeName := "normal_user" if user.UserType.UserTypeName != userTypeName { t.Errorf("user.UserType.UserTypeName should be '%s', we got '%s'", userTypeName, user.UserType.UserTypeName) } decimalSeparator := "." if user.NumberFormat.DecimalSeparator != decimalSeparator { t.Errorf("user.NumberFormat.DecimalSeparator should be '%s', we got '%s'", decimalSeparator, user.NumberFormat.DecimalSeparator) } groupingSeparator := "," if user.NumberFormat.GroupingSeparator != groupingSeparator { t.Errorf("user.NumberFormat.GroupingSeparator should be '%s', we got '%s'", groupingSeparator, user.NumberFormat.GroupingSeparator) } groupPattern := "###,##0.##" if user.NumberFormat.GroupPattern != groupPattern { t.Errorf("user.NumberFormat.GroupPattern should be '%s', we got '%s'", groupPattern, user.NumberFormat.GroupPattern) } } func TestDecodeSlicePath(t *testing.T) { var document = `[{"name":"bill"},{"name":"lisa"}]` type NameDoc struct { Name string `jpath:"name"` } sliceScript := []byte(document) var sliceMap []map[string]interface{} json.Unmarshal(sliceScript, &sliceMap) var myslice1 []NameDoc err1 := DecodeSlicePath(sliceMap, &myslice1) var myslice2 []*NameDoc err2 := DecodeSlicePath(sliceMap, &myslice2) name1 := "bill" name2 := "lisa" if err1 != nil { t.Fatal(err1) } if err2 != nil { t.Fatal(err1) } if myslice1[0].Name != name1 { t.Errorf("myslice1[0].Name should be '%s', we got '%s'", name1, myslice1[0].Name) } if myslice1[1].Name != name2 { t.Errorf("myslice1[1].Name should be '%s', we got '%s'", name1, myslice1[1].Name) } if myslice2[0].Name != name1 { t.Errorf("myslice2[0].Name should be '%s', we got '%s'", name1, myslice1[0].Name) } if myslice2[1].Name != name2 { t.Errorf("myslice1[1].Name should be '%s', we got '%s'", name2, myslice2[1].Name) } } func TestDecodeWithEmbeddedSlice(t *testing.T) { var document string = `{ "cobrandId": 10010352, "channelId": -1, "locale": "en_US", "tncVersion": 2, "categories":["rabbit","bunny","frog"], "people": [ { "name": "jack", "age": { "birth":10, "year":2000, "animals": [ { "barks":"yes", "tail":"yes" }, { "barks":"no", "tail":"yes" } ] } }, { "name": "jill", "age": { "birth":11, "year":2001 } } ] }` type Animal struct { Barks string `jpath:"barks"` } type People struct { Age int `jpath:"age.birth"` Animals []Animal `jpath:"age.animals"` } type Items struct { Categories []string `jpath:"categories"` Peoples []People `jpath:"people"` } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) items := Items{} DecodePath(docMap, &items) if len(items.Categories) != 3 { t.Error("items.Categories did not decode") return } if len(items.Peoples) != 2 { t.Error("items.Peoples did not decode") return } if len(items.Peoples[0].Animals) != 2 { t.Error("items.Peoples[0].Animals did not decode") return } age := 10 if items.Peoples[0].Age != 10 { t.Errorf("items.Peoples[0].Age should be '%d', we got '%s'", age, items.Peoples[0].Age) } barks := "yes" if items.Peoples[0].Animals[0].Barks != barks { t.Errorf("items.Peoples[0].Animals[0].Barks should be '%d', we got '%s'", barks, items.Peoples[0].Animals[0].Barks) } } func TestDecodeWithAbstractField(t *testing.T) { var document = `{"Error":[{"errorDetail":"Invalid Cobrand Credentials"}]}` type AnError struct { Error []map[string]interface{} `jpath:"Error"` } type Context struct { *AnError } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) context := Context{} DecodePath(docMap, &context) errorDetail := "Invalid Cobrand Credentials" if context.Error[0]["errorDetail"].(string) != errorDetail { t.Errorf("context.Error[0][\"errorDetail\"] should be '%s', we got '%s'", errorDetail, context.Error[0]["errorDetail"].(string)) } } func TestDecodePointerToPointer(t *testing.T) { var document = `{"Error":[{"errorDetail":"Invalid Cobrand Credentials"}]}` type AnError struct { Error []map[string]interface{} `jpath:"Error"` } type APointerError struct { *AnError } type Context struct { *APointerError } docScript := []byte(document) var docMap map[string]interface{} json.Unmarshal(docScript, &docMap) var context Context DecodePath(docMap, &context) errorDetail := "Invalid Cobrand Credentials" if context.Error != nil { if context.Error[0]["errorDetail"].(string) != errorDetail { t.Errorf("context.Error[0][\"errorDetail\"] should be '%s', we got '%s'", errorDetail, context.Error[0]["errorDetail"].(string)) } } }