0

My question is about parsing JSON files in golang.

Specifically, I am trying to parse output of a command "docker network inspect bridge", which happens to be in JSON format. The command is described here. My goal is to get a list of "IPv4Address" for the listed containers.

I have tried to do this but failing to convert map[string]interface{} to map[string]string. My code is here:- https://play.golang.org/p/eO_j996gGb

$ sudo docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "b2b1a2cba717161d984383fd68218cf70bbbd17d328496885f7c921333228b0f",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {
                    "Subnet": "172.17.42.1/16",
                    "Gateway": "172.17.42.1"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727": {
                "Name": "container2",
                "EndpointID": "0aebb8fcd2b282abe1365979536f21ee4ceaf3ed56177c628eae9f706e00e019",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27": {
                "Name": "container1",
                "EndpointID": "a00676d9c91a96bbe5bcfb34f705387a33d7cc365bac1a29e4e9728df92d10ad",
                "MacAddress": "02:42:ac:11:00:01",
                "IPv4Address": "172.17.0.1/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        }
    }
]

What is the correct way to parse such JSON files in golang. I did try using type conversion but doesn't help. I have tried many things but finally could only get as far as the code shown in the snippet. How can I extract the IPv4Address field from the "cont" object?

Link to go playground snippet I have is https://play.golang.org/p/eO_j996gGb. Any help is greatly appreciated (willing to try things out, don't need just the code but ideas :) Thanks.

1
  • More an FYI: Most docker commands accept a --format option which needs to be a Golang Template (from the text/template) package. Commented Oct 4, 2016 at 14:25

1 Answer 1

3

Your code just needs to be modified to further unpackage the data. You're leaving it with each container object represented as an interface{} which makes it so you can't access the fields within it. Another assert on each to make it a map[string]interface{} will allow you to access the fields within by name (like IPv4Address). Here's a working example; https://play.golang.org/p/4OC5axN4Gd

Here's the important code change;

    containers := (foo[0]["Containers"]).(map[string]interface{})

    //fmt.Printf("containers %+v", containers)
    for _, v := range containers {
        unwrapped := v.(map[string]interface{})
        fmt.Printf("\ncont %+v\n", unwrapped["IPv4Address"])            
    }

v is just an interface{} so you have no way of accessing the IPv4Address field without asserting again/converting into a type.

Now that works fine, however, I would recommend getting away from the heavy use of interfaces in your code. Your code is full of unsafe operations that create a need for a lot of error handling (like every time you attempt access into the map you have the potential to throw, as well as anytime you do a type assert). There are plenty of examples of how to do this here on SO but if you leave a comment requesting it I can produce another example that will unmarshal into structs. This is a lot more safe and maintainable imo because most errors will be raised on the call to unmarshal, rather than having potentially a lot of code that works with the results that could all cause a panic.

Here is some play code that has all the types in it and uses unmarshal; https://play.golang.org/p/VfHst9GaiR

Your types can be represented with the following;

type DockerInstance struct {
    Name string `json:"Name"`
    Id string `json:"Id"`
    Scope string `json:"Scope"`
    Driver string `json:"Driver"`   
    EnableIPv6 bool `json:"EnableIPv6"`
    IPAM IPAM `json:"IPAM"`
    Internal bool `json:"Internal"`
    Containers map[string]Container `json:"Containers"`
    Options map[string]string `json:"Options"`
    Labels interface{} `json:"Labels"`
}

type IPAM struct {
    Driver string `json:"Driver"`
    Options interface{} `json:"Options"`
    Config []Conf `json:"Config"`
}

type Conf struct {
    Subnet string `json:"Subnet"`
}

type Container struct {
    Name string `json:"Name"`
        EndPointID string `json:"EndpointID"`
    MacAddress string `json:"MacAddress"`
        IPv4Address string `json:"IPv4Address"`
    IPv6Address string `json:"IPv6Address"`
}

There's a few stuff I still have set to interface, that's just cause the sample json doesn't include any data for them so I can't know what their type should be in the Go source. Here's some basic stuff I also added in your main to test out the objects/make sure I had things defined correctly;

docks := []DockerInstance{} //note you're using an array of the top level type
err := json.Unmarshal([]byte(teststring), &docks)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println()
    fmt.Println()
    fmt.Println(docks[0].Name)
}

for k, v := range docks[0].Containers {
    fmt.Println(k)
    fmt.Println(v.IPv4Address)
}

for k, v := range docks[0].Options {
      fmt.Println(k)
    fmt.Println(v)
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you very much. I came very close to the code as in your solution while trying things out yesterday but sometimes things just don't work :(. Yes, I am interested in the safer way you explained in your answer. Can you please provide that too for this JSON. Thanks again.
Actually, I just need to have an equivalent struct representing the JSON and Unmarshal into it (if thats what you meant). I can try this myself if you give some pointers. Thanks.
@GauravSinha yep, that's what I mean and here is an example of that; play.golang.org/p/VfHst9GaiR I'll post some of the relevant code into my answer though I've done a lot of sloppy stuff just to prove the data is there/show how to access it.
@GauravSinha yep. I also included the json tags, they're not necessary in this particular example though because the field names in Go exactly match those in the json. It's quite common that you have to use those though (the most obvious example is when the fields in json are lower cased, you usually need them to be exported in Go which makes them different than what's in the json).
@GauravSinha I think the two are basically equivalent, I just used unmarshal because I'm used to it and I think it's more convenient. I'd have to do a bit of research to be sure about that.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.