Reputation: 3118
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.
Upvotes: 0
Views: 1193
Reputation: 48086
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)
}
Upvotes: 3