map.go raw

   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