2

I'm currently learning golang and (probably as many others before me) I'm trying to properly understand the empty interface.

As an exercise, I'm reading a big json file produced by Postman and trying to access just one field (out of the many available).

Here is a simple representation of the json without the unnecessary fields I don't want to read (but that are still there):

{ "results": [ { "times": [ 1, 2, 3, 4 ] } ] } 

Since the json object is big, I opted out of unmarshaling it with a custom struct, and rather decided to use the empty interface interface{}

After some time, I managed to get some working code, but I'm quite sure this isn't the correct way of doing it.

byteValue, _ := ioutil.ReadAll(jsonFile) var result map[string]interface{} err = json.Unmarshal(byteValue, &result) if err != nil { log.Fatalln(err) } // ESPECIALLY UGLY r := result["results"].([]interface{}) r1 := r[0].(map[string]interface{}) r2 := r1["times"].([]interface{}) times := make([]float64, len(r2)) for i := range r2 { times[i] = r2[i].(float64) } 

Is there a better way to navigate through my json object without having to instantiate new variables every time i move deeper and deeper into the object?

2
  • 3
    If you use interface{} because you don't want to pre-declare a struct, you have to do the "especially ugly" thing. If you pre-declare a struct you don't have to. It's a trade-off. Commented Jul 12, 2021 at 15:40
  • 3
    Get used to mholt.github.io/json-to-go for starters, on how you can pre-define structs for your JSON and unmarshal to those structs Commented Jul 12, 2021 at 15:43

2 Answers 2

4
  1. Even if the JSON is large, you only have to define the fields you actually care about
  2. You only need to use JSON tags if the keys aren't valid Go identifiers (keys are idents in this case), even then you can sometimes avoid it by using a map[string]something
  3. Unless you need the sub structs for some function or whatnot, you don't need to define them
  4. Unless you need to reuse the type, you don't even have to define that, you can just define the struct at declaration time

Example:

package main import ( "encoding/json" "fmt" ) const s = ` { "results": [ { "times": [1, 2, 3, 4] } ] } ` func main() { var t struct { Results []struct { Times []int } } json.Unmarshal([]byte(s), &t) fmt.Printf("%+v\n", t) // {Results:[{Times:[1 2 3 4]}]} } 
Sign up to request clarification or add additional context in comments.

Comments

2

[...] trying to access just one field (out of the many available).

For this concrete use case I would use a library to query and access to a single value in a known path like:

https://github.com/jmespath/go-jmespath

In the other hand, if you're practicing how to access nested values in a JSON, I would recommend you to give a try to write a recursive function that follows a path in an unknown structure the same way (but simple) like go-jmespath does.

Ok, I challenged myself and spent an hour writing this. It works. Not sure about performance or bugs and it's really limited :)

https://play.golang.org/p/dlIsmG6Lk-p

package main import ( "encoding/json" "errors" "fmt" "strings" ) func main() { // I Just added a bit more of data to the structure to be able to test different paths fileContent := []byte(` {"results": [ {"times": [ 1, 2, 3, 4 ]}, {"times2": [ 5, 6, 7, 8 ]}, {"username": "rosadabril"}, {"age": 42}, {"location": [41.5933262, 1.8376757]} ], "more_results": { "nested_1": { "nested_2":{ "foo": "bar" } } } }`) var content map[string]interface{} if err := json.Unmarshal(fileContent, &content); err != nil { panic(err) } // some paths to test valuePaths := []string{ "results.times", "results.times2", "results.username", "results.age", "results.doesnotexist", "more_results.nested_1.nested_2.foo", } for _, p := range valuePaths { breadcrumbs := strings.Split(p, ".") value, err := search(breadcrumbs, content) if err != nil { fmt.Printf("\nerror searching '%s': %s\n", p, err) continue } fmt.Printf("\nFOUND A VALUE IN: %s\n", p) fmt.Printf("Type: %T\nValue: %#v\n", value, value) } } // search is our fantastic recursive function! The idea is to search in the structure in a very basic way, for complex querying use jmespath func search(breadcrumbs []string, content map[string]interface{}) (interface{}, error) { // we should never hit this point, but better safe than sorry and we could incurr in an out of range error (check line 82) if len(breadcrumbs) == 0 { return nil, errors.New("ran out of breadcrumbs :'(") } // flag that indicates if we are at the end of our trip and whe should return the value without more checks lastBreadcrumb := len(breadcrumbs) == 1 // current breadcrumb is always the first element. currentBreadcrumb := breadcrumbs[0] if value, found := content[currentBreadcrumb]; found { if lastBreadcrumb { return value, nil } // if the value is a map[string]interface{}, go down the rabbit hole, recursion! if aMap, isAMap := value.(map[string]interface{}); isAMap { // we are calling ourselves popping the first breadcrumb and passing the current map return search(breadcrumbs[1:], aMap) } // if it's an array of interfaces the thing gets complicated :( if anArray, isArray := value.([]interface{}); isArray { for _, something := range anArray { if aMap, isAMap := something.(map[string]interface{}); isAMap && len(breadcrumbs) > 1 { if v, err := search(breadcrumbs[1:], aMap); err == nil { return v, nil } } } } } return nil, errors.New("woops, nothing here") } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.