1package objx 2 3import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "io/ioutil" 8 "net/url" 9 "strings" 10) 11 12// MSIConvertable is an interface that defines methods for converting your 13// custom types to a map[string]interface{} representation. 14type MSIConvertable interface { 15 // MSI gets a map[string]interface{} (msi) representing the 16 // object. 17 MSI() map[string]interface{} 18} 19 20// Map provides extended functionality for working with 21// untyped data, in particular map[string]interface (msi). 22type Map map[string]interface{} 23 24// Value returns the internal value instance 25func (m Map) Value() *Value { 26 return &Value{data: m} 27} 28 29// Nil represents a nil Map. 30var Nil = New(nil) 31 32// New creates a new Map containing the map[string]interface{} in the data argument. 33// If the data argument is not a map[string]interface, New attempts to call the 34// MSI() method on the MSIConvertable interface to create one. 35func New(data interface{}) Map { 36 if _, ok := data.(map[string]interface{}); !ok { 37 if converter, ok := data.(MSIConvertable); ok { 38 data = converter.MSI() 39 } else { 40 return nil 41 } 42 } 43 return Map(data.(map[string]interface{})) 44} 45 46// MSI creates a map[string]interface{} and puts it inside a new Map. 47// 48// The arguments follow a key, value pattern. 49// 50// 51// Returns nil if any key argument is non-string or if there are an odd number of arguments. 52// 53// Example 54// 55// To easily create Maps: 56// 57// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) 58// 59// // creates an Map equivalent to 60// m := objx.Map{"name": "Mat", "age": 29, "subobj": objx.Map{"active": true}} 61func MSI(keyAndValuePairs ...interface{}) Map { 62 newMap := Map{} 63 keyAndValuePairsLen := len(keyAndValuePairs) 64 if keyAndValuePairsLen%2 != 0 { 65 return nil 66 } 67 for i := 0; i < keyAndValuePairsLen; i = i + 2 { 68 key := keyAndValuePairs[i] 69 value := keyAndValuePairs[i+1] 70 71 // make sure the key is a string 72 keyString, keyStringOK := key.(string) 73 if !keyStringOK { 74 return nil 75 } 76 newMap[keyString] = value 77 } 78 return newMap 79} 80 81// ****** Conversion Constructors 82 83// MustFromJSON creates a new Map containing the data specified in the 84// jsonString. 85// 86// Panics if the JSON is invalid. 87func MustFromJSON(jsonString string) Map { 88 o, err := FromJSON(jsonString) 89 if err != nil { 90 panic("objx: MustFromJSON failed with error: " + err.Error()) 91 } 92 return o 93} 94 95// FromJSON creates a new Map containing the data specified in the 96// jsonString. 97// 98// Returns an error if the JSON is invalid. 99func FromJSON(jsonString string) (Map, error) { 100 var m Map 101 err := json.Unmarshal([]byte(jsonString), &m) 102 if err != nil { 103 return Nil, err 104 } 105 m.tryConvertFloat64() 106 return m, nil 107} 108 109func (m Map) tryConvertFloat64() { 110 for k, v := range m { 111 switch v.(type) { 112 case float64: 113 f := v.(float64) 114 if float64(int(f)) == f { 115 m[k] = int(f) 116 } 117 case map[string]interface{}: 118 t := New(v) 119 t.tryConvertFloat64() 120 m[k] = t 121 case []interface{}: 122 m[k] = tryConvertFloat64InSlice(v.([]interface{})) 123 } 124 } 125} 126 127func tryConvertFloat64InSlice(s []interface{}) []interface{} { 128 for k, v := range s { 129 switch v.(type) { 130 case float64: 131 f := v.(float64) 132 if float64(int(f)) == f { 133 s[k] = int(f) 134 } 135 case map[string]interface{}: 136 t := New(v) 137 t.tryConvertFloat64() 138 s[k] = t 139 case []interface{}: 140 s[k] = tryConvertFloat64InSlice(v.([]interface{})) 141 } 142 } 143 return s 144} 145 146// FromBase64 creates a new Obj containing the data specified 147// in the Base64 string. 148// 149// The string is an encoded JSON string returned by Base64 150func FromBase64(base64String string) (Map, error) { 151 decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) 152 decoded, err := ioutil.ReadAll(decoder) 153 if err != nil { 154 return nil, err 155 } 156 return FromJSON(string(decoded)) 157} 158 159// MustFromBase64 creates a new Obj containing the data specified 160// in the Base64 string and panics if there is an error. 161// 162// The string is an encoded JSON string returned by Base64 163func MustFromBase64(base64String string) Map { 164 result, err := FromBase64(base64String) 165 if err != nil { 166 panic("objx: MustFromBase64 failed with error: " + err.Error()) 167 } 168 return result 169} 170 171// FromSignedBase64 creates a new Obj containing the data specified 172// in the Base64 string. 173// 174// The string is an encoded JSON string returned by SignedBase64 175func FromSignedBase64(base64String, key string) (Map, error) { 176 parts := strings.Split(base64String, SignatureSeparator) 177 if len(parts) != 2 { 178 return nil, errors.New("objx: Signed base64 string is malformed") 179 } 180 181 sig := HashWithKey(parts[0], key) 182 if parts[1] != sig { 183 return nil, errors.New("objx: Signature for base64 data does not match") 184 } 185 return FromBase64(parts[0]) 186} 187 188// MustFromSignedBase64 creates a new Obj containing the data specified 189// in the Base64 string and panics if there is an error. 190// 191// The string is an encoded JSON string returned by Base64 192func MustFromSignedBase64(base64String, key string) Map { 193 result, err := FromSignedBase64(base64String, key) 194 if err != nil { 195 panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) 196 } 197 return result 198} 199 200// FromURLQuery generates a new Obj by parsing the specified 201// query. 202// 203// For queries with multiple values, the first value is selected. 204func FromURLQuery(query string) (Map, error) { 205 vals, err := url.ParseQuery(query) 206 if err != nil { 207 return nil, err 208 } 209 m := Map{} 210 for k, vals := range vals { 211 m[k] = vals[0] 212 } 213 return m, nil 214} 215 216// MustFromURLQuery generates a new Obj by parsing the specified 217// query. 218// 219// For queries with multiple values, the first value is selected. 220// 221// Panics if it encounters an error 222func MustFromURLQuery(query string) Map { 223 o, err := FromURLQuery(query) 224 if err != nil { 225 panic("objx: MustFromURLQuery failed with error: " + err.Error()) 226 } 227 return o 228} 229