2

I am trying to access some information stored in a json file via Go. I have two related issues. One is that I'm not sure how to organize my structs and secondly how do I access them via a variable. I'll notate my code to make a little more sense

// To be clear, this is dummy info and I'm linting my actual json
// data. It loads fine, I just don't want to get hung up on this side
{
 "A": {
        "lob": "A",
        "url": [
                 "example.com",
                 "test.com"]
}
 "B": {
        "lob": "B",
        "url": [
                 "example2.com",
                 "test2.com"]

}
}

So the concern is that the structure of the options is identical. I am building this as part of a REST AP. The hope is that users can use http://testapi.com/getdata/A and it will return the urls and name info under A and likewise for B. As is, it loads both of them as separate components of the same struct:

type SiteList struct {
    A struct {
        Lob string   `json:"lob"`
        URL []string `json:"url"`
    } `json:"test"`
    B struct {
        Lob string   `json:"lob"`
        URL []string `json:"url"`
    } `json:"test2"`
}

I can do .A or .B by hand but I'm wondering how to handle it when the requests come in so that my API will only return the data under A or B.

Steve B
  • 75
  • 1
  • 8
  • 1
    Use a map if fields names are not known at compile time or to access fields dynamically. E.g. `type SiteList map[string]Site`. (Consider not calling it list though; that's just misleading). – Peter Apr 19 '18 at 11:19

3 Answers3

1

If you're going to consume the API via accessing the API via http://testapi.com/getdata/A or http://testapi.com/getdata/B then A and B can be considered the parameters that drive the behavior of your API.

If you're passing A, you basically want to access the site data associated with A and if you're passing B, the site data for B should be returned.

An easy way to organize this data internally is to use a dedicated Go type site which holds Lob and URL and arrange everything in a map via map[string]site, which is initialized on startup of your server.

You can then dynamically access the parameter given to your API (A or B, but can be easily extended), lookup the site information from the map and, in case it's a valid site, return the corresponding data encoded as JSON.

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type site struct {
    Lob string   `json:"lob"`
    URL []string `json:"url"`
}

var sites = map[string]site{
    "A": site{
        Lob: "A",
        URL: []string{
            "example.com",
            "test.com",
        },
    },
    "B": site{
        Lob: "B",
        URL: []string{
            "example2.com",
            "test2.com",
        },
    },
}

const endpoint = "/getdata/"

func handler(w http.ResponseWriter, r *http.Request) {
    lob := r.URL.Path[len(endpoint):]
    s, ok := sites[lob]
    if !ok {
        w.WriteHeader(http.StatusNotFound)
        return
    }

    resp, err := json.Marshal(s)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json; charset=UTF-8")
    w.Write(resp)
}

func main() {
    http.HandleFunc(endpoint, handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
fhe
  • 6,099
  • 1
  • 41
  • 44
  • You rule. I can't look at it until after work but I suspect this is exactly what I needed – Steve B Apr 20 '18 at 16:34
  • @SteveB Any success with the proposed approach? – fhe Apr 24 '18 at 06:21
  • 1
    The answer ended up being a mashup of this and a solution I modified from somewhere else. Part of my solution was making my json a []structs that I could iterate through. Not long after, I figured out gorilla's schema which made passing values really simple. That you to everyone who helped. I am marking this answer as solved because it absolutely worked as expected. My json structure was separate – Steve B May 02 '18 at 02:57
0

Missing values in the json will be unmarshalled with their zero values. An int will be 0, a string will be "", a map and any pointer will be nil. Structs will be initialized, but their fields will have the zero value.

In your case, when comcast is missing in the json, B will be initialized as a struct, where Lob is "" and URL is an empty slice of string.

mbuechmann
  • 5,413
  • 5
  • 27
  • 40
  • While this is good info and I will certainly pocket it for another time, my current concern is not missing values, I know that url will always be a []string, for example. I am looking of how to use a varialbe as a struct parameter. Imagine I have bigstruct which is a struct of type Sitelist that has two structs (A and B) in it, as above. I have a variable that val := "A" and I want to use that variable to pull just the info for A. I essentially want bigstruct.A but I need to deliver A or B depending on the value of that variable – Steve B Apr 19 '18 at 20:33
  • In this case I think that your question was a bit unclear. What exactly do you want to know? – mbuechmann Apr 20 '18 at 06:40
0

If I understand correctly you have a struct SiteList and depending on if they user navigates to /getData/A or /getData/B you want to serve SiteList.A or SiteList.B.

From the json marshal docs here or this SO answer you can leave out some fields of a struct when they are empty. Basically if there's no data in that field then it will not appear in the marshalled json.

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

With that in mind, if you have the control in the handlers of the requests to be able to set the values in SiteList according to the path then you could make use of this feature.

If you extract the shared parts of A and B into a struct:

type Inner struct {
    Lob string `json:"lob"`
    URL []string `json:"url"`
}

// using pointers here for A and B means setting to nil is empty
type SiteList struct {
    A *Inner `json:"test,omitempty"`
    B *Inner `json:"test2,omitempty"`
}

And then set the one that you do not want to be in the response body to nil. (nil is empty for a pointer, so it will not get marshalled.)

Zak
  • 5,515
  • 21
  • 33