Documentation ¶
Overview ¶
Package dyno is a utility to work with dynamic objects at ease.
Primary goal is to easily handle dynamic objects and arrays (and a mixture of these) that are the result of unmarshaling a JSON or YAML text into an interface{} for example. When unmarshaling into interface{}, libraries usually choose either map[string]interface{} or map[interface{}]interface{} to represent objects, and []interface{} to represent arrays. Package dyno supports a mixture of these in any depth and combination.
When operating on a dynamic object, you designate a value you're interested in by specifying a path. A path is a navigation; it is a series of map keys and int slice indices that tells how to get to the value.
Should you need to marshal a dynamic object to JSON which contains maps with interface{} key type (which is not supported by encoding/json), you may use the ConvertMapI2MapS converter function.
The implementation does not use reflection at all, so performance is rather good.
Let's see a simple example editing a JSON text to mask out a password. This is a simplified version of the Example_jsonEdit example function:
src := `{"login":{"password":"secret","user":"bob"},"name":"cmpA"}` var v interface{} if err := json.Unmarshal([]byte(src), &v); err != nil { panic(err) } // Edit (mask out) password: if err = dyno.Set(v, "xxx", "login", "password"); err != nil { fmt.Printf("Failed to set password: %v\n", err) } edited, err := json.Marshal(v) fmt.Printf("Edited JSON: %s, error: %v\n", edited, err)
Output will be:
Edited JSON: {"login":{"password":"xxx","user":"bob"},"name":"cmpA"}, error: <nil>
Example ¶
Example shows a few of dyno's features, such as getting, setting and appending values to / from a dynamic object.
package main import ( "encoding/json" "fmt" "os" "github.com/icza/dyno" ) func main() { person := map[string]interface{}{ "name": map[string]interface{}{ "first": "Bob", "last": "Archer", }, "age": 22, "fruits": []interface{}{ "apple", "banana", }, } // pp prints the person pp := func(err error) { json.NewEncoder(os.Stdout).Encode(person) // Output JSON if err != nil { fmt.Println("ERROR:", err) } } // Print initial person and its first name: pp(nil) v, err := dyno.Get(person, "name", "first") fmt.Printf("First name: %v, error: %v\n", v, err) // Change first name: pp(dyno.Set(person, "Alice", "name", "first")) // Change complete name from map to a single string: pp(dyno.Set(person, "Alice Archer", "name")) // Print and increment age: age, err := dyno.GetInt(person, "age") fmt.Printf("Age: %v, error: %v\n", age, err) pp(dyno.Set(person, age+1, "age")) // Change a fruits slice element: pp(dyno.Set(person, "lemon", "fruits", 1)) // Add a new fruit: pp(dyno.Append(person, "melon", "fruits")) }
Output: {"age":22,"fruits":["apple","banana"],"name":{"first":"Bob","last":"Archer"}} First name: Bob, error: <nil> {"age":22,"fruits":["apple","banana"],"name":{"first":"Alice","last":"Archer"}} {"age":22,"fruits":["apple","banana"],"name":"Alice Archer"} Age: 22, error: <nil> {"age":23,"fruits":["apple","banana"],"name":"Alice Archer"} {"age":23,"fruits":["apple","lemon"],"name":"Alice Archer"} {"age":23,"fruits":["apple","lemon","melon"],"name":"Alice Archer"}
Example (JsonEdit) ¶
Example_jsonEdit shows a simple example how JSON can be edited. The password placed in the JSON is masked out.
package main import ( "encoding/json" "fmt" "github.com/icza/dyno" ) func main() { src := `{"login":{"password":"secret","user":"bob"},"name":"cmpA"}` fmt.Printf("Input JSON: %s\n", src) var v interface{} if err := json.Unmarshal([]byte(src), &v); err != nil { panic(err) } user, err := dyno.Get(v, "login", "user") fmt.Printf("User: %-6s, error: %v\n", user, err) password, err := dyno.Get(v, "login", "password") fmt.Printf("Password: %-6s, error: %v\n", password, err) // Edit (mask out) password: if err = dyno.Set(v, "xxx", "login", "password"); err != nil { fmt.Printf("Failed to set password: %v\n", err) } edited, err := json.Marshal(v) fmt.Printf("Edited JSON: %s, error: %v\n", edited, err) }
Output: Input JSON: {"login":{"password":"secret","user":"bob"},"name":"cmpA"} User: bob , error: <nil> Password: secret, error: <nil> Edited JSON: {"login":{"password":"xxx","user":"bob"},"name":"cmpA"}, error: <nil>
Index ¶
- func Append(v interface{}, value interface{}, path ...interface{}) error
- func AppendMore(v interface{}, values []interface{}, path ...interface{}) error
- func ConvertMapI2MapS(v interface{}) interface{}
- func Delete(v interface{}, key interface{}, path ...interface{}) error
- func Get(v interface{}, path ...interface{}) (interface{}, error)
- func GetBoolean(v interface{}, path ...interface{}) (bool, error)
- func GetFloat64(v interface{}, path ...interface{}) (float64, error)
- func GetFloating(v interface{}, path ...interface{}) (float64, error)
- func GetInt(v interface{}, path ...interface{}) (int, error)
- func GetInteger(v interface{}, path ...interface{}) (int64, error)
- func GetMapI(v interface{}, path ...interface{}) (map[interface{}]interface{}, error)
- func GetMapS(v interface{}, path ...interface{}) (map[string]interface{}, error)
- func GetSlice(v interface{}, path ...interface{}) ([]interface{}, error)
- func GetString(v interface{}, path ...interface{}) (string, error)
- func SGet(m map[string]interface{}, path ...string) (interface{}, error)
- func SSet(m map[string]interface{}, value interface{}, path ...string) error
- func Set(v interface{}, value interface{}, path ...interface{}) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Append ¶
func Append(v interface{}, value interface{}, path ...interface{}) error
Append appends a value to a slice denoted by the path.
The slice denoted by path must already exist.
Path cannot be empty or nil, else an error is returned.
Example ¶
package main import ( "fmt" "github.com/icza/dyno" ) func main() { m := map[string]interface{}{ "a": []interface{}{ "3", 2, []interface{}{1, "two", 3.3}, }, } printMap := func(err error) { fmt.Println(m) if err != nil { fmt.Println("ERROR:", err) } } printMap(dyno.Append(m, 4, "a")) printMap(dyno.Append(m, 9, "a", 2)) printMap(dyno.Append(m, 1, "x")) }
Output: map[a:[3 2 [1 two 3.3] 4]] map[a:[3 2 [1 two 3.3 9] 4]] map[a:[3 2 [1 two 3.3 9] 4]] ERROR: missing key: x (path element idx: 0)
func AppendMore ¶
func AppendMore(v interface{}, values []interface{}, path ...interface{}) error
AppendMore appends values to a slice denoted by the path.
The slice denoted by path must already exist.
Path cannot be empty or nil, else an error is returned.
Example ¶
package main import ( "fmt" "github.com/icza/dyno" ) func main() { m := map[string]interface{}{ "ints": []interface{}{ 1, 2, }, } err := dyno.AppendMore(m, []interface{}{3, 4, 5}, "ints") fmt.Println(m, err) }
Output: map[ints:[1 2 3 4 5]] <nil>
func ConvertMapI2MapS ¶
func ConvertMapI2MapS(v interface{}) interface{}
ConvertMapI2MapS walks the given dynamic object recursively, and converts maps with interface{} key type to maps with string key type. This function comes handy if you want to marshal a dynamic object into JSON where maps with interface{} key type are not allowed.
Recursion is implemented into values of the following types:
-map[interface{}]interface{} -map[string]interface{} -[]interface{}
When converting map[interface{}]interface{} to map[string]interface{}, fmt.Sprint() with default formatting is used to convert the key to a string key.
Example ¶
package main import ( "encoding/json" "fmt" "github.com/icza/dyno" ) func main() { m := map[interface{}]interface{}{ 1: "one", "numbers": []interface{}{2, 3, 4.4}, } // m cannot be marshaled using encoding/json: data, err := json.Marshal(m) fmt.Printf("JSON: %q, error: %v\n", data, err) m2 := dyno.ConvertMapI2MapS(m) // But m2 can be: data, err = json.Marshal(m2) fmt.Printf("JSON: %s, error: %v\n", data, err) }
Output: JSON: "", error: json: unsupported type: map[interface {}]interface {} JSON: {"1":"one","numbers":[2,3,4.4]}, error: <nil>
func Delete ¶
func Delete(v interface{}, key interface{}, path ...interface{}) error
Delete deletes a key from a map or an element from a slice denoted by the path.
Deleting a non-existing map key is a no-op. Attempting to delete a slice element from a slice with invalid index is an error.
Path cannot be empty or nil if v itself is a slice, else an error is returned.
Example ¶
package main import ( "fmt" "github.com/icza/dyno" ) func main() { m := map[string]interface{}{ "name": "Bob", "ints": []interface{}{ 1, 2, 3, }, } err := dyno.Delete(m, "name") fmt.Println(m, err) err = dyno.Delete(m, 1, "ints") fmt.Println(m, err) err = dyno.Delete(m, "ints") fmt.Println(m, err) }
Output: map[ints:[1 2 3]] <nil> map[ints:[1 3]] <nil> map[] <nil>
func Get ¶
func Get(v interface{}, path ...interface{}) (interface{}, error)
Get returns a value denoted by the path.
If path is empty or nil, v is returned.
Example ¶
package main import ( "fmt" "github.com/icza/dyno" ) func main() { m := map[string]interface{}{ "a": 1, "b": map[interface{}]interface{}{ 3: []interface{}{1, "two", 3.3}, }, } printValue := func(v interface{}, err error) { fmt.Printf("Value: %-5v, Error: %v\n", v, err) } printValue(dyno.Get(m, "a")) printValue(dyno.Get(m, "b", 3, 1)) printValue(dyno.Get(m, "x")) sl, _ := dyno.Get(m, "b", 3) // This is: []interface{}{1, "two", 3.3} printValue(dyno.Get(sl, 4)) }
Output: Value: 1 , Error: <nil> Value: two , Error: <nil> Value: <nil>, Error: missing key: x (path element idx: 0) Value: <nil>, Error: index out of range: 4 (path element idx: 0)
func GetBoolean ¶
GetBoolean returns a bool value denoted by the path.
This function accepts many different types and converts them to bool, namely:
-boolean type -integer and floating point types (false for zero values, true otherwise) -string (fmt.Sscan() will be used for parsing)
If path is empty or nil, v is returned as a bool.
func GetFloat64 ¶
GetFloat64 returns a float64 value denoted by the path.
If path is empty or nil, v is returned as a float64.
func GetFloating ¶
GetFloating returns a float64 value denoted by the path.
This function accepts many different types and converts them to float64, namely:
-floating point types (float64, float32) -integer types (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64) (which implies the aliases byte and rune too) -string (fmt.Sscan() will be used for parsing) -any type with a Float64() (float64, error) method (e.g. json.Number)
If path is empty or nil, v is returned as an int64.
func GetInt ¶
GetInt returns an int value denoted by the path.
If path is empty or nil, v is returned as an int.
func GetInteger ¶
GetInteger returns an int64 value denoted by the path.
This function accepts many different types and converts them to int64, namely:
-integer types (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64) (which implies the aliases byte and rune too) -floating point types (float64, float32) -string (fmt.Sscan() will be used for parsing) -any type with an Int64() (int64, error) method (e.g. json.Number)
If path is empty or nil, v is returned as an int64.
func GetMapI ¶
func GetMapI(v interface{}, path ...interface{}) (map[interface{}]interface{}, error)
GetMapI returns a map with interface{} keys denoted by the path.
If path is empty or nil, v is returned as a slice.
func GetMapS ¶
GetMapS returns a map with string keys denoted by the path.
If path is empty or nil, v is returned as a slice.
func GetSlice ¶
func GetSlice(v interface{}, path ...interface{}) ([]interface{}, error)
GetSlice returns a slice denoted by the path.
If path is empty or nil, v is returned as a slice.
func GetString ¶
GetString returns a string value denoted by the path.
If path is empty or nil, v is returned as a string.
func SGet ¶
SGet returns a value denoted by the path consisting of only string keys.
SGet is an optimized and specialized version of the general Get. The path may only contain string map keys (no slice indices), and each value associated with the keys (being the path elements) must also be maps with string keys, except the value asssociated with the last path element.
If path is empty or nil, m is returned.
func SSet ¶
SSet sets a map element with string key type, denoted by the path consisting of only string keys.
SSet is an optimized and specialized version of the general Set. The path may only contain string map keys (no slice indices), and each value associated with the keys (being the path elements) must also be a maps with string keys, except the value associated with the last path element.
The map denoted by the preceding path before the last path element must already exist.
Path cannot be empty or nil, else an error is returned.
func Set ¶
func Set(v interface{}, value interface{}, path ...interface{}) error
Set sets a map or slice element denoted by the path.
The last element of the path must be a map key or a slice index, and the preceding path must denote a map or a slice respectively which must already exist.
Path cannot be empty or nil, else an error is returned.
Example ¶
package main import ( "encoding/json" "fmt" "os" "github.com/icza/dyno" ) func main() { m := map[string]interface{}{ "a": 1, "b": map[string]interface{}{ "3": []interface{}{1, "two", 3.3}, }, } printMap := func(err error) { json.NewEncoder(os.Stdout).Encode(m) // Output JSON if err != nil { fmt.Println("ERROR:", err) } } printMap(dyno.Set(m, 2, "a")) printMap(dyno.Set(m, "owt", "b", "3", 1)) printMap(dyno.Set(m, 1, "x")) sl, _ := dyno.Get(m, "b", "3") // This is: []interface{}{1, "owt", 3.3} printMap(dyno.Set(sl, 1, 4)) }
Output: {"a":2,"b":{"3":[1,"two",3.3]}} {"a":2,"b":{"3":[1,"owt",3.3]}} {"a":2,"b":{"3":[1,"owt",3.3]},"x":1} {"a":2,"b":{"3":[1,"owt",3.3]},"x":1} ERROR: index out of range: 4 (path element idx: 0)
Types ¶
This section is empty.