go

Is there a better way to parse this JSON?


I'm new to Golang. I'm trying to figure out how to parse a JSON response from an HTTPS request. I've done this before by unmarshalling into a struct, but I couldn't get that working for this JSON, possibly because it starts with an 'unnamed' array? Not sure.

Below is a shortened sample of the JSON. The only part I need are the tokens. I'd like to just print them out.

[
 {"queryId":"a5e0cde8-462b-4df2-b275-dca7b89aa903",
  "queryType":"Batch",
  "description":null,
  "tokens":
  [
   {
    "name":"Device",
    "type":"STRING",
    "isList":true,
    "default":"all",
    "description":"The device identifier."
   },
   {
    "name":"StartUsageTime",
    "type":"TIMESTAMP",
    "isList":false,
    "default":"1900-1-1",
    "description":"Start datetime."
   },
   {
    "name":"EndUsageTime",
    "type":"TIMESTAMP",
    "isList":false,
    "default":"9999-12-31",
    "description":"End datetime."
   }
  ]
 }
]

My code (below) works. But the part where it's ranging over 'tokens' is ugly and feels clunky to me. Note: I removed all of the error checking for posting here; it is in the actual code.

func getRequest(authToken string) {
  reqUrl := fmt.Sprintf("https://host.com?params")
  req, err := http.NewRequest(http.MethodGet, reqUrl, nil)
  
  req.Header.Set("Authorization", "Bearer "+authToken)
  resp, err := http.DefaultClient.Do(req)
  
  if resp.Body != nil {
    defer resp.Body.Close()
  }
  
  body, err := io.ReadAll(resp.Body)
  
  var jbody []any
  err = json.Unmarshal(body, &jbody)
  
  for _, value := range jbody {
    theMap := value.(map[string]any)
    tokens := theMap["tokens"].([]any)
    for _, v := range tokens {
      fmt.Printf(" - Name: %s\n", v.(map[string]any)["name"])
      fmt.Printf("   - Type: %s\n", v.(map[string]any)["type"])
      fmt.Printf("   - IsList: %t\n", v.(map[string]any)["isList"])
      fmt.Printf("   - Default: %s\n", v.(map[string]any)["default"])
      fmt.Printf("   - Description: %s\n\n", v.(map[string]any)["description"])
    }
  }
}

Is there a better way to access those 'tokens'? Or a way to unmarshall this JSON into a struct? I could not figure that out.


Solution

  • You can Define Structs

    type Token struct {
        Name        string `json:"name"`
        Type        string `json:"type"`
        IsList      bool   `json:"isList"`
        Default     string `json:"default"`
        Description string `json:"description"`
    }
    
    type Query struct {
        QueryID     string  `json:"queryId"`
        QueryType   string  `json:"queryType"`
        Description *string `json:"description"`
        Tokens      []Token `json:"tokens"`
    }
    

    Update your function

    func getRequest(authToken string) {
        reqUrl := fmt.Sprintf("https://host.com?params")
        req, err := http.NewRequest(http.MethodGet, reqUrl, nil)
        
        req.Header.Set("Authorization", "Bearer "+authToken)
        resp, err := http.DefaultClient.Do(req)
        
        if resp.Body != nil {
            defer resp.Body.Close()
        }
        
        body, err := io.ReadAll(resp.Body)
        
        var queries []Query
        err = json.Unmarshal(body, &queries)
        
        for _, query := range queries {
            for _, token := range query.Tokens {
                fmt.Printf(" - Name: %s\n", token.Name)
                fmt.Printf("   - Type: %s\n", token.Type)
                fmt.Printf("   - IsList: %t\n", token.IsList)
                fmt.Printf("   - Default: %s\n", token.Default)
                fmt.Printf("   - Description: %s\n\n", token.Description)
            }
        }
    }