Accessing a nested value in a Go map - go

I have a random JSON (I will not know the schema ahead of time) that I am marshaling into an map[string]interface{}.
I also have a string representing the field value I would like to return, something like "SomeRootKey.NestValue.AnotherNestValue"
I want to be able to return that value. Is there an simple way to access that value without doing some recursive tricks?

Without recursion? yes, using a loop, but no there's no magical way to do that.
func getKey(m interface{}, key string) (string, bool) {
L:
for _, k := range strings.Split(key, ".") {
var v interface{}
switch m := m.(type) {
case map[string]interface{}:
v = m[k]
case []interface{}:
idx, err := strconv.Atoi(k)
if err != nil || idx > len(m) {
break L
}
v = m[idx]
default:
break L
}
switch v := v.(type) {
case map[string]interface{}:
m = v
case []interface{}:
m = v
case string:
return v, true
default:
break L
}
}
return "", false
}
Using json like:
{
"SomeRootKey": {
"NestValue": {"AnotherNestValue": "object value"},
"Array": [{"AnotherNestValue": "array value"}]
}
}
You can use:
fmt.Println(getKey(m, "SomeRootKey.NestValue.AnotherNestValue"))
fmt.Println(getKey(m, "SomeRootKey.Array.0.AnotherNestValue"))
playground

Related

Is it possible to infer type parameters from what return values are assigned to?

Suppose I wrote two functions like this:
func ToInterfaceSlice[T any](s []T) []interface{} {
res := make([]interface{}, len(s))
for i, v := range s {
res[i] = v
}
return res
}
func FromInterfaceSlice[T any](s []interface{}) (res []T, err error) {
res = make([]T, len(s))
for i, v := range s {
vt, ok := v.(T)
if !ok {
return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
}
res[i] = vt
}
return
}
When I parse type from the input parameters, I can simply use
var m = []int{1, 2, 3}
fmt.Println(ToInterfaceSlice(m))
The compiler knows the T is int.
However when I try passing type from the return variables
var m []int
m, _ = FromInterfaceSlice([]interface{}{1, 2, 3})
fmt.Println(m)
The compiler gives error:
.\scratch.go:29:27: cannot infer T
I must explicitly pass the type in the function call:
var m []int
m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
fmt.Println(m)
Is there anything hard to infer type parameters from return type when the receiver vars are not interface? Or just not implemented, even not to implement on purpose?
Update #1 after the comment
I do know a, b := GenericFunc() cannot refer the type of returned value. Currently Go does have "it depends" case whether requires the explicit instantiation or not from the user input.
type Set[T comparable] map[T]struct{}
func NewSet[T comparable](eles ...T) Set[T] {
s := make(Set[T])
for _, ele := range eles {
s[ele] = struct{}{}
}
return s
}
It's okay to use both t := NewSet(1, 2, 3) and t := NewSet[string](), but not var t NewSet[float64] = NewSet() now because of this
The current rules for type inference are explicit. How the return values are used is not taken into account:
Type inference is based on
a type parameter list
a substitution map M initialized with the known type arguments, if any
a (possibly empty) list of ordinary function arguments (in case of a function call only)
As of Go 1.18 might simply rewrite your function to accept an argument of the required type; this has also the benefit of not hiding allocations inside the function body:
func FromInterfaceSlice[T any](s []interface{}, dst []T) error {
if len(s) != len(dst) {
return errors.New("lengths don't match")
}
for i, v := range s {
vt, ok := v.(T)
if !ok {
return nil, fmt.Errorf("%v (type=%T) doesn't fit the target type %T", v, v, res)
}
dst[i] = vt
}
return nil
}
And pass in a destination slice with the required length:
func main() {
src := []interface{}{1, 2, 3}
m := make([]int, len(src))
_ = FromInterfaceSlice(src, m)
fmt.Println(m)
}
If you can't or don't want to determine the slice's length beforehand, you are left with explicit instantiation:
var m []int
m, _ = FromInterfaceSlice[int]([]interface{}{1, 2, 3})
// ^^^ explicit type argument
Also the type parameters are still not inferrable with := shorthand declaration:
// what is m???
m, err := FromInterfaceSlice([]interface{}{1, 2, 3})

How to return a slice of an specific type depending on the variable i send to the function in Go

I have a function, that takes an empty interface (any type, which im looking for 2 in specific) and then returning a slice of the selected type.
func testingInterface(temp interface{}) (interface{}, interface{}) {
var doc interface{}
array := make([]interface{}, 3)
switch x := temp.(type) {
case int:
doc = x
tempArray := make([]string, 3)
for i, v := range tempArray {
array[i] = string(v)
}
fmt.Printf("Int to string %T, %T ", doc, tempArray)
case string:
doc = x
tempArray := make([]int, 3)
for i, v := range tempArray {
array[i] = int(v)
}
fmt.Printf("String to int %T, %T ", doc, tempArray)
}
return array, doc
}
So what happens, it is that the doc variable indeed changes the type of it, but the slice when i return it, it stays as []interface{}
When i test an element individual, it changes the type but the whole array it is still an []interface{}
The tempArray in the question has the slice you want. Return it instead of copying the values to the []interface{} that you don't want.
Use this code:
func testingInterface(x interface{}) (interface{}, interface{}) {
var result interface{}
switch x.(type) {
case int:
result = make([]int, 3)
case string:
result = make([]string, 3)
}
return result, x
}

Add custom method for slice string[]

I'm creating a utility package for my project.
Many of my string slices need a function to
1. remove duplicates
2. remove empty strings
I know 1 way to do this:1. Add a function for each case which accepts a string slice and returns a string slice
func removeEmpty(s []string) []string {
i := 0 // i points to next available pos
for _, v := range s {
if v != "" {
s[i] = v
i++
}
}
return s[:i]
}
func dedup(s []string) []string {
i := 0 // i points to next available pos
mp := map[string]bool{}
for _, v := range s {
if _, ok := mp[v]; !ok {
s[i] = v
mp[v] = true
i++
}
}
return s[:i]
}
when I apply these 2 functions to my slice, I can do:
mySlice := string[]{}
mySlice = dedup(removeEmpty(mySlice))
I want to make it somthing like:
mySlice = mySlice.dedup().removeEmpty()
or
mySlice.dedup().removeEmpty()
Maybe I can add custom method to slice[] ?
I tried writing it
func (s []string) removeEmpty() {
}
I'm getting complie error.
You can't define methods on []string, but you can define your own type based on []string and define methods on that:
type myStringSlice []string
func (s myStringSlice) removeEmpty() myStringSlice {
i := 0 // i points to next available pos
for _, v := range s {
if v != "" {
s[i] = v
i++
}
}
return s[:i]
}
func (s myStringSlice) dedup() myStringSlice {
i := 0 // i points to next available pos
mp := map[string]bool{}
for _, v := range s {
if _, ok := mp[v]; !ok {
s[i] = v
mp[v] = true
i++
}
}
return s[:i]
}
See https://play.golang.org/p/u1z_N3c_wPP.
As mentioned in the documentation:
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package.
So you have to declare a type on []string and then declare the method on your own type.

check for equality on slices without order

I am trying to find a solution to check for equality in 2 slices. Unfortanely, the answers I have found require values in the slice to be in the same order. For example, http://play.golang.org/p/yV0q1_u3xR evaluates equality to false.
I want a solution that lets []string{"a","b","c"} == []string{"b","a","c"} evaluate to true.
MORE EXAMPLES
[]string{"a","a","c"} == []string{"c","a","c"} >>> false
[]string{"z","z","x"} == []string{"x","z","z"} >>> true
Here is an alternate solution, though perhaps a bit verbose:
func sameStringSlice(x, y []string) bool {
if len(x) != len(y) {
return false
}
// create a map of string -> int
diff := make(map[string]int, len(x))
for _, _x := range x {
// 0 value for int is 0, so just increment a counter for the string
diff[_x]++
}
for _, _y := range y {
// If the string _y is not in diff bail out early
if _, ok := diff[_y]; !ok {
return false
}
diff[_y] -= 1
if diff[_y] == 0 {
delete(diff, _y)
}
}
return len(diff) == 0
}
Try it on the Go Playground
You can use cmp.Diff together with cmpopts.SortSlices:
less := func(a, b string) bool { return a < b }
equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == ""
Here is a full example that runs on the Go Playground:
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func main() {
x := []string{"a", "b", "c"}
y := []string{"a", "c", "b"}
less := func(a, b string) bool { return a < b }
equalIgnoreOrder := cmp.Diff(x, y, cmpopts.SortSlices(less)) == ""
fmt.Println(equalIgnoreOrder) // prints "true"
}
The other answers have better time complexity O(N) vs (O(N log(N)), that are in my answer, also my solution will take up more memory if elements in the slices are repeated frequently, but I wanted to add it because I think this is the most straight forward way to do it:
package main
import (
"fmt"
"sort"
"reflect"
)
func array_sorted_equal(a, b []string) bool {
if len(a) != len(b) {return false }
a_copy := make([]string, len(a))
b_copy := make([]string, len(b))
copy(a_copy, a)
copy(b_copy, b)
sort.Strings(a_copy)
sort.Strings(b_copy)
return reflect.DeepEqual(a_copy, b_copy)
}
func main() {
a := []string {"a", "a", "c"}
b := []string {"c", "a", "c"}
c := []string {"z","z","x"}
d := []string {"x","z","z"}
fmt.Println( array_sorted_equal(a, b))
fmt.Println( array_sorted_equal(c, d))
}
Result:
false
true
I would think the easiest way would be to map the elements in each array/slice to their number of occurrences, then compare the maps:
func main() {
x := []string{"a","b","c"}
y := []string{"c","b","a"}
xMap := make(map[string]int)
yMap := make(map[string]int)
for _, xElem := range x {
xMap[xElem]++
}
for _, yElem := range y {
yMap[yElem]++
}
for xMapKey, xMapVal := range xMap {
if yMap[xMapKey] != xMapVal {
return false
}
}
return true
}
You'll need to add some additional due dilligence, like short circuiting if your arrays/slices contain elements of different types or are of different length.
Generalizing the code of testify ElementsMatch, solution to compare any kind of objects (in the example []map[string]string):
https://play.golang.org/p/xUS2ngrUWUl
Like adrianlzt wrote in his answer, an implementation of assert.ElementsMatch from testify can be used to achieve that. But how about reusing actual testify module instead of copying that code when all you need is a bool result of the comparison? The implementation in testify is intended for tests code and usually takes testing.T argument.
It turns out that ElementsMatch can be quite easily used outside of testing code. All it takes is a dummy implementation of an interface with ErrorF method:
type dummyt struct{}
func (t dummyt) Errorf(string, ...interface{}) {}
func elementsMatch(listA, listB interface{}) bool {
return assert.ElementsMatch(dummyt{}, listA, listB)
}
Or test it on The Go Playground, which I've adapted from the adrianlzt's example.
Since I haven't got enough reputation to comment, I have to post yet another answer with a bit better code readability:
func AssertSameStringSlice(x, y []string) bool {
if len(x) != len(y) {
return false
}
itemAppearsTimes := make(map[string]int, len(x))
for _, i := range x {
itemAppearsTimes[i]++
}
for _, i := range y {
if _, ok := itemAppearsTimes[i]; !ok {
return false
}
itemAppearsTimes[i]--
if itemAppearsTimes[i] == 0 {
delete(itemAppearsTimes, i)
}
}
if len(itemAppearsTimes) == 0 {
return true
}
return false
}
The logic is the same as in this answer
I know its been answered but still I would like to add my answer. By following code here stretchr/testify we can have something like
func Elementsmatch(listA, listB []string) (string, bool) {
aLen := len(listA)
bLen := len(listB)
if aLen != bLen {
return fmt.Sprintf("Len of the lists don't match , len listA %v, len listB %v", aLen, bLen), false
}
visited := make([]bool, bLen)
for i := 0; i < aLen; i++ {
found := false
element := listA[i]
for j := 0; j < bLen; j++ {
if visited[j] {
continue
}
if element == listB[j] {
visited[j] = true
found = true
break
}
}
if !found {
return fmt.Sprintf("element %s appears more times in %s than in %s", element, listA, listB), false
}
}
return "", true
}
Now lets talk about performance of this solution compared to map based ones. Well it really depends on the size of the lists which you are comparing, If size of list is large (I would say greater than 20) then map approach is better else this would be sufficent.
Well on Go PlayGround it shows 0s always, but run this on local system and you can see the difference in time taken as size of list increases
So the solution I propose is, adding map based comparision from above solution
func Elementsmatch(listA, listB []string) (string, bool) {
aLen := len(listA)
bLen := len(listB)
if aLen != bLen {
return fmt.Sprintf("Len of the lists don't match , len listA %v, len listB %v", aLen, bLen), false
}
if aLen > 20 {
return elementsMatchByMap(listA, listB)
}else{
return elementsMatchByLoop(listA, listB)
}
}
func elementsMatchByLoop(listA, listB []string) (string, bool) {
aLen := len(listA)
bLen := len(listB)
visited := make([]bool, bLen)
for i := 0; i < aLen; i++ {
found := false
element := listA[i]
for j := 0; j < bLen; j++ {
if visited[j] {
continue
}
if element == listB[j] {
visited[j] = true
found = true
break
}
}
if !found {
return fmt.Sprintf("element %s appears more times in %s than in %s", element, listA, listB), false
}
}
return "", true
}
func elementsMatchByMap(x, y []string) (string, bool) {
// create a map of string -> int
diff := make(map[string]int, len(x))
for _, _x := range x {
// 0 value for int is 0, so just increment a counter for the string
diff[_x]++
}
for _, _y := range y {
// If the string _y is not in diff bail out early
if _, ok := diff[_y]; !ok {
return fmt.Sprintf(" %v is not present in list b", _y), false
}
diff[_y] -= 1
if diff[_y] == 0 {
delete(diff, _y)
}
}
if len(diff) == 0 {
return "", true
}
return "", false
}

How to read an slice of like []interface{} in Go?

I have something like this:
a := []interface{}{}
b := []interface{}{}
type S struct {
text string
}
s := S{"string"}
t := S{"string"}
a = append(a, s)
b = append(b, t)
a := append(a, b)
a
And now I want to read elements of a, or elements of elements.. but how?
What you want is called a type assertion. http://golang.org/ref/spec#Type_assertions
The simple example on that page is:
var x interface{} = 7 // x has dynamic type int and value 7
i := x.(int) // i has type int and value 7`
The other thing to note is that a type assertion returns a value called ok that is true if the assertion is successful. Here's a simple code example for your case:
a := []interface{}{}
b := []interface{}{}
type S struct {
text string
}
s := S{"string"}
t := S{"string"}
a = append(a, s)
b = append(b, t)
a = append(a, b)
assertedS,ok := a[0].(S)
if !ok { // If this is, in fact, not a value of type S, something is wrong
// error handling
}
fmt.Println(assertedS) // Should show you the same thing as printing s
assertedB,ok := a[1].([]interface{})
if !ok {
//...
}
assertedT,ok := assertedB[0].(S)
if !ok {
//...
}
fmt.Println(assertedT) // Should show you the same thing as printing t
If you don't know ahead of time which list element is what, you can iterate through it and use the "type switch". http://golang.org/ref/spec#Switch_statements
switch x.(type) {
// cases
}
Which allows you to perform conditional behavior based on what type the stored interface{} really is.
For instance, you might use
func ExtractSlice(a []interface{}) {
for _,x := range a {
switch i := x.(type) {
case S:
fmt.Println(i)
case []interface{}:
ExtractSlice(i) // Recursively unpacks b once it's found within a
}
}
}
Do you mean this?
a := []interface{}{}
b := []interface{}{}
type S struct {
text string
}
s := S{"string"}
t := S{"string"}
a = append(a, s)
b = append(b, t)
a = append(a, b)
for _, v := range a {
switch v.(type) {
case S:
fmt.Println("S", v)
default:
fmt.Println("Slice", v)
}
}
This code example may help:
package main
import "fmt"
func main() {
a := []interface{}{}
b := []interface{}{}
type S struct {
text string
}
s := S{"string s"}
t := S{"string t"}
a = append(a, s)
b = append(b, t)
a = append(a, b)
for _, v := range a {
fmt.Println(v)
}
}
but be aware that you've defined a and b as slices of interfaces. This means, that when you do a = append(a, b) you're putting the b slice after the existing a string in the a slice, and therefore when you range over a you get:
{string s} //interface of string
[{string t}] //slice of interface of string

Resources