Синтаксический анализ JSON в go

#json #go

#json #Вперед

Вопрос:

Мой вопрос касается синтаксического анализа файлов JSON в golang.

В частности, я пытаюсь проанализировать вывод команды «docker network inspect bridge», которая оказывается в формате JSON. Команда описана здесь . Моя цель — получить список «IPv4address» для перечисленных контейнеров.

Я пытался это сделать, но не смог преобразовать интерфейс map[string]{} в строку map[string] . Мой код здесь:- 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"
        }
    }
]
  

Каков правильный способ анализа таких файлов JSON в golang. Я пробовал использовать преобразование типов, но это не помогает. Я перепробовал много вещей, но, наконец, смог добраться только до кода, показанного во фрагменте. Как я могу извлечь поле IPv4address из объекта «cont»?

Ссылка на фрагмент go playground у меня есть https://play.golang.org/p/eO_j996gGb . Любая помощь приветствуется (готов попробовать, нужен не только код, но и идеи 🙂 Спасибо.

Комментарии:

1. К вашему сведению: большинство команд docker принимают --format параметр, который должен быть шаблоном Golang (из text/template пакета).

Ответ №1:

Ваш код просто нужно изменить для дальнейшей распаковки данных. Вы оставляете его с каждым объектом контейнера, представленным как an interface{} , что делает его таким, что вы не можете получить доступ к полям внутри него. Другое утверждение для каждого, чтобы сделать его a map[string]interface{} , позволит вам получить доступ к полям внутри по имени (например IPv4Address ). Вот рабочий пример; https://play.golang.org/p/4OC5axN4Gd

Вот важное изменение кода;

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

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

v это просто interface{} так, что у вас нет возможности получить доступ к IPv4Address полю без повторного утверждения / преобразования в тип.

Однако теперь это работает нормально, я бы рекомендовал отказаться от интенсивного использования интерфейсов в вашем коде. Ваш код полон небезопасных операций, которые создают необходимость в большой обработке ошибок (например, каждый раз, когда вы пытаетесь получить доступ к карте, которую вы можете выбросить, а также в любое время, когда вы выполняете утверждение типа). Есть много примеров того, как это сделать здесь, на SO, но если вы оставите комментарий с просьбой об этом, я могу привести другой пример, который будет разделен на структуры. Это намного более безопасно и удобно для обслуживания imo, потому что большинство ошибок будет возникать при вызове unmarshal, вместо того, чтобы иметь потенциально много кода, который работает с результатами, которые могут вызвать панику.

Вот некоторый код воспроизведения, который содержит все типы и использует unmarshal; https://play.golang.org/p/VfHst9GaiR

Ваши типы могут быть представлены следующим образом;

 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"`
}
  

Есть несколько вещей, которые я все еще установил для интерфейса, это просто потому, что образец json не содержит для них никаких данных, поэтому я не могу знать, какой их тип должен быть в исходном коде Go. Вот некоторые базовые вещи, которые я также добавил в ваш основной, чтобы протестировать объекты / убедиться, что у меня все определено правильно;

 docks := []DockerInstance{} //note you're using an array of the top level type
err := json.Unmarshal([]byte(teststring), amp;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)
}
  

Комментарии:

1. Большое вам спасибо. Я очень близко подошел к коду, как в вашем решении, когда вчера пробовал что-то, но иногда что-то просто не работает :(. Да, меня интересует более безопасный способ, который вы объяснили в своем ответе. Не могли бы вы предоставить это и для этого JSON. Еще раз спасибо.

2. На самом деле, мне просто нужно иметь эквивалентную структуру, представляющую JSON и Unmarshal в нее (если это то, что вы имели в виду). Я могу попробовать это сам, если вы дадите несколько советов. Спасибо.

3. @GauravSinha да, это то, что я имею в виду, и вот пример этого; play.golang.org/p/VfHst9GaiR Я опубликую часть соответствующего кода в своем ответе, хотя я сделал много небрежных вещей, просто чтобы доказать, что данные есть / показать, как получить к ним доступ.

4. @GauravSinha да. Я также включил теги json, они не нужны в этом конкретном примере, потому что имена полей в Go точно соответствуют именам в json. Довольно часто вам приходится их использовать (наиболее очевидный пример — когда поля в json имеют нижний регистр, обычно их нужно экспортировать в Go, что отличает их от того, что есть в json).

5. @GauravSinha Я думаю, что эти два в основном эквивалентны, я просто использовал unmarshal, потому что я привык к этому, и я думаю, что это удобнее. Мне нужно было бы провести небольшое исследование, чтобы быть уверенным в этом.