1 package objx
2 3 import (
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.
14 type 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).
22 type Map map[string]interface{}
23 24 // Value returns the internal value instance
25 func (m Map) Value() *Value {
26 return &Value{data: m}
27 }
28 29 // Nil represents a nil Map.
30 var 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.
35 func 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 // Returns nil if any key argument is non-string or if there are an odd number of arguments.
51 //
52 // # Example
53 //
54 // To easily create Maps:
55 //
56 // m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
57 //
58 // // creates an Map equivalent to
59 // m := objx.Map{"name": "Mat", "age": 29, "subobj": objx.Map{"active": true}}
60 func MSI(keyAndValuePairs ...interface{}) Map {
61 newMap := Map{}
62 keyAndValuePairsLen := len(keyAndValuePairs)
63 if keyAndValuePairsLen%2 != 0 {
64 return nil
65 }
66 for i := 0; i < keyAndValuePairsLen; i = i + 2 {
67 key := keyAndValuePairs[i]
68 value := keyAndValuePairs[i+1]
69 70 // make sure the key is a string
71 keyString, keyStringOK := key.(string)
72 if !keyStringOK {
73 return nil
74 }
75 newMap[keyString] = value
76 }
77 return newMap
78 }
79 80 // ****** Conversion Constructors
81 82 // MustFromJSON creates a new Map containing the data specified in the
83 // jsonString.
84 //
85 // Panics if the JSON is invalid.
86 func MustFromJSON(jsonString string) Map {
87 o, err := FromJSON(jsonString)
88 if err != nil {
89 panic("objx: MustFromJSON failed with error: " + err.Error())
90 }
91 return o
92 }
93 94 // MustFromJSONSlice creates a new slice of Map containing the data specified in the
95 // jsonString. Works with jsons with a top level array
96 //
97 // Panics if the JSON is invalid.
98 func MustFromJSONSlice(jsonString string) []Map {
99 slice, err := FromJSONSlice(jsonString)
100 if err != nil {
101 panic("objx: MustFromJSONSlice failed with error: " + err.Error())
102 }
103 return slice
104 }
105 106 // FromJSON creates a new Map containing the data specified in the
107 // jsonString.
108 //
109 // Returns an error if the JSON is invalid.
110 func FromJSON(jsonString string) (Map, error) {
111 var m Map
112 err := json.Unmarshal([]byte(jsonString), &m)
113 if err != nil {
114 return Nil, err
115 }
116 return m, nil
117 }
118 119 // FromJSONSlice creates a new slice of Map containing the data specified in the
120 // jsonString. Works with jsons with a top level array
121 //
122 // Returns an error if the JSON is invalid.
123 func FromJSONSlice(jsonString string) ([]Map, error) {
124 var slice []Map
125 err := json.Unmarshal([]byte(jsonString), &slice)
126 if err != nil {
127 return nil, err
128 }
129 return slice, nil
130 }
131 132 // FromBase64 creates a new Obj containing the data specified
133 // in the Base64 string.
134 //
135 // The string is an encoded JSON string returned by Base64
136 func FromBase64(base64String string) (Map, error) {
137 decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
138 decoded, err := ioutil.ReadAll(decoder)
139 if err != nil {
140 return nil, err
141 }
142 return FromJSON(string(decoded))
143 }
144 145 // MustFromBase64 creates a new Obj containing the data specified
146 // in the Base64 string and panics if there is an error.
147 //
148 // The string is an encoded JSON string returned by Base64
149 func MustFromBase64(base64String string) Map {
150 result, err := FromBase64(base64String)
151 if err != nil {
152 panic("objx: MustFromBase64 failed with error: " + err.Error())
153 }
154 return result
155 }
156 157 // FromSignedBase64 creates a new Obj containing the data specified
158 // in the Base64 string.
159 //
160 // The string is an encoded JSON string returned by SignedBase64
161 func FromSignedBase64(base64String, key string) (Map, error) {
162 parts := strings.Split(base64String, SignatureSeparator)
163 if len(parts) != 2 {
164 return nil, errors.New("objx: Signed base64 string is malformed")
165 }
166 167 sig := HashWithKey(parts[0], key)
168 if parts[1] != sig {
169 return nil, errors.New("objx: Signature for base64 data does not match")
170 }
171 return FromBase64(parts[0])
172 }
173 174 // MustFromSignedBase64 creates a new Obj containing the data specified
175 // in the Base64 string and panics if there is an error.
176 //
177 // The string is an encoded JSON string returned by Base64
178 func MustFromSignedBase64(base64String, key string) Map {
179 result, err := FromSignedBase64(base64String, key)
180 if err != nil {
181 panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
182 }
183 return result
184 }
185 186 // FromURLQuery generates a new Obj by parsing the specified
187 // query.
188 //
189 // For queries with multiple values, the first value is selected.
190 func FromURLQuery(query string) (Map, error) {
191 vals, err := url.ParseQuery(query)
192 if err != nil {
193 return nil, err
194 }
195 m := Map{}
196 for k, vals := range vals {
197 m[k] = vals[0]
198 }
199 return m, nil
200 }
201 202 // MustFromURLQuery generates a new Obj by parsing the specified
203 // query.
204 //
205 // For queries with multiple values, the first value is selected.
206 //
207 // Panics if it encounters an error
208 func MustFromURLQuery(query string) Map {
209 o, err := FromURLQuery(query)
210 if err != nil {
211 panic("objx: MustFromURLQuery failed with error: " + err.Error())
212 }
213 return o
214 }
215