3

I am storing JSON in a file in which it has nested objects.

The structure looks like this:

{
  "users" : {
    "enxtropayy": {
      "pass": "",
      "root": true,
      "admin": true,
      "moderator": true
    }
  }
}

Soooo, I want to be able to access the "enxtropayy" user via Go:

usr, _ := ioutil.ReadFile("userInfo.JSON")
var data map[string]interface{}
json.Unmarshal(usr,&data)
fmt.Println(data["users"])

It prints map[enxtropayy:map[admin:true moderator:true pass: root:true]]

This is great and all, but I can't go deeper than one layer, and I found out that it's because interfaces don't have keys.

So instead I re-wrote my Go code to look like this:

var data interface{}
json.Unmarshal(usr,&data)
fmt.Println(data.(interface{}).(map[string]interface{})["users"].(interface{}).(map[string]interface{})["enxtropayy"])

It works! But, I have to do .(map[string]interface{})["keyName"] every time I want to access another nested layer and that doesn't seem very "neat" and is a bit tedious to write. It also seems unnecessary and is probably not the best practice. The whole "type assertion" and "interfaces/maps" is new to me so I don't know much about what I'm doing, which is something I want to improve on.

Is there another way I can type assert an interface to a map (or a better way to store the JSON in Go altogether), and could you also explain how it works (give a man a fish and he will eat for a day; teach a man to fish and he will eat for a living)?

Reality
  • 637
  • 1
  • 6
  • 25
  • 4
    To avoid constant type assertions when reading a json unmarshalled data object, it is better to define a Go struct that mimics the JSON structure and unmarshal into that instead of a `map[string]interface{}`. In the case that you can't rely on the structure of the JSON data, then of course it's only natural that you need to assert the type of things before using them (and should be checking to see if the assertion fails with the comma ok pattern). – Hymns For Disco Mar 25 '21 at 17:33
  • @HymnsForDisco Sounds good! I'll try to find out how to make the struct have a dynamic structure (since the JSON will change to allocate more users in the future). – Reality Mar 25 '21 at 17:38
  • 3
    You can't have a dynamic structure, but if only the users are changing, it could be e.g. `map[string]UserInfo`, making the structure of each user static but the list of users dynamic. – Adrian Mar 25 '21 at 17:41
  • That is exactly something along the lines of what I want to do! I'll have to look into how to do that. – Reality Mar 25 '21 at 17:42

1 Answers1

4

It's better to just use struct when possible:

package main

import (
   "encoding/json"
   "fmt"
)

const s = `
{
   "users" : {
      "enxtropayy": {
         "admin": true, "moderator": true, "root": true,
         "pass": ""
      }
   }
}
`

func main() {
   var t struct {
      Users map[string]struct {
         Admin, Moderator, Root bool
         Pass string
      }
   }
   json.Unmarshal([]byte(s), &t)
   // {Users:map[enxtropayy:{Admin:true Moderator:true Root:true Pass:}]}
   fmt.Printf("%+v\n", t)
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407
  • 3
    Go is so weird from other languages I know IMO that it's almost counterintuitive to what I know. This is quite clear on eradicating my suspicions on how to do the recommended strategy in the comments. Thanks! – Reality Mar 25 '21 at 17:47
  • There *is* a problem though, I can't edit the `Pass` value. Is there a way I can directly change this? Oh, wait, fixed! Solution: make the pass a pointer and then just reference it directly (wow, I cannot believe how easy that was)! – Reality Mar 25 '21 at 18:33
  • 1
    @Reality https://stackoverflow.com/questions/42716852/how-to-update-map-values-in-go I'm guessing this might help. If not, look for other questions or ask a new one. (oops, you already got it). – Hymns For Disco Mar 25 '21 at 18:37
  • @HymnsForDisco thanks (I came to the same conclusion as one of the answers)! Sometimes I have to remember that I have a brain and can use it lol. I've worked with pointers enough to understand them a bit, but I'm still surprised it works. – Reality Mar 25 '21 at 18:39
  • 1
    Yeah the `json` package is pretty smart, it uses reflection to determine how to appropriately store the value into your struct, even with pointers vs normal values. Another option is to use a pattern like `map[string]*UserInfo` so that you can update all your fields if need be. – Hymns For Disco Mar 25 '21 at 18:42