SeattleOrBayArea
SeattleOrBayArea

Reputation: 3118

Parsing JSON in go

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

Answers (1)

evanmcdonnal
evanmcdonnal

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

Related Questions