【问题标题】:Parse JSON HTTP response using golang使用 golang 解析 JSON HTTP 响应
【发布时间】:2018-01-27 02:27:44
【问题描述】:

我正在尝试从以下 curl 输出中获取 say "ip" 的值:

{  
  "type":"example",
  "data":{  
    "name":"abc",
    "labels":{  
      "key":"value"
    }
  },
  "subsets":[  
    {  
      "addresses":[  
        {  
          "ip":"192.168.103.178"
        }
      ],
      "ports":[  
        {  
          "port":80
        }
      ]
    }
  ]
}

我在互联网上找到了许多解析 curl 请求的 json 输出的示例,并且我编写了以下代码,但这似乎并没有返回我说 "ip" 的值

package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)

type svc struct {
    Ip string `json:"ip"`
}

func main() {

url := "http://myurl.com"

testClient := http.Client{
    Timeout: time.Second * 2, // Maximum of 2 secs
}

req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
    log.Fatal(err)
}


res, getErr := testClient.Do(req)
if getErr != nil {
    log.Fatal(getErr)
}

body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
    log.Fatal(readErr)
}

svc1 := svc{}
jsonErr := json.Unmarshal(body, &svc1)
if jsonErr != nil {
    log.Fatal(jsonErr)
}

fmt.Println(svc1.Ip)
}

如果有人能提示我需要添加到我的代码中以获得say“ip”的值,我将不胜感激。

【问题讨论】:

  • 您的 json 无效。您在ip 正下方有一个额外的},以及两个对象的最后一项(在80 和之后)的逗号(,"192.168.103.178")。您可以使用任何在线 json 格式化程序/验证程序来帮助您构建有效的 json - 只需 google 一下,您就会发现几个选项。
  • 对不起ip下面的额外括号和额外的逗号只是复制和粘贴错误;实际的卷曲输出非常大,因此为了这个问题不得不缩短它。我现在已经在问题中解决了它。
  • 您的 struct 与 JSON 输入的结构不匹配。您正在尝试反序列化一个大型数据结构,其中 ip 字段深深嵌套在数组中,成为单个对象,其中包含一个包含单个值的顶级字段 - 如果subsets 或 @ 中有多个项目怎么办987654332@?它怎么知道subsets[n].addresses[m].ip 应该映射到.ip
  • 您似乎也将 curl Linux 实用程序与 Go 的 http.Client 混淆了。它们根本不是一回事。
  • @Adrian 是的,你是对的,在实际 curl 输出中的地址下有多个项目,但我感兴趣的是子集 [n].addresses[m].ip 将始终映射到知识产权。

标签: json go


【解决方案1】:

您可以创建反映您的 json 结构的结构,然后解码您的 json。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
)

type Example struct {
    Type    string   `json:"type,omitempty"`
    Subsets []Subset `json:"subsets,omitempty"`
}

type Subset struct {
    Addresses []Address `json:"addresses,omitempty"`
}

type Address struct {
    IP string `json:"IP,omitempty"`
}

    func main() {

    m := []byte(`{"type":"example","data": {"name": "abc","labels": {"key": "value"}},"subsets": [{"addresses": [{"ip": "192.168.103.178"}],"ports": [{"port": 80}]}]}`)

    r := bytes.NewReader(m)
    decoder := json.NewDecoder(r)

    val := &Example{}
    err := decoder.Decode(val)

    if err != nil {
        log.Fatal(err)
    }

    // If you want to read a response body
    // decoder := json.NewDecoder(res.Body)
    // err := decoder.Decode(val)

    // Subsets is a slice so you must loop over it
    for _, s := range val.Subsets {
        // within Subsets, address is also a slice
        // then you can access each IP from type Address
        for _, a := range s.Addresses {
            fmt.Println(a.IP)
        }
    }

}

输出将是: 192.168.103.178

通过将其解码为结构,您可以遍历任何切片,而不是将自己限制在一个 IP

此处示例:

https://play.golang.org/p/sWA9qBWljA

【讨论】:

  • 感谢这种结构化的方法!但是,在我的情况下,我有来自 url 的 json 输出,而不是您定义的 m(如我的问题所示)。那么,我该如何修改你的代码的第 25 行呢?
  • 我将更新我的答案以包含我在响应正文中阅读的示例
  • 一个问题-如何从 [{[{192.168.103.178}]}] 检索 ip(即 192.168.103.178)?我尝试了 strings.Trim 功能,但抱怨cannot use ip (type []Subset) as type string in argument to strings.Trim
  • 我已经更新了 go playground 中的示例,请检查该链接
  • 新代码 @play.golang.org/p/sWA9qBWljA 完全返回 192.168.103.178,检查一下 ;)
【解决方案2】:

一种方法是将 JSON 解组为 map,例如(假设 jsData 包含 JSON 字符串)

obj := map[string]interface{}{}
if err := json.Unmarshal([]byte(jsData), &obj); err != nil {
    log.Fatal(err)
}

接下来,实现一个函数,以递归方式从映射中搜索与键关联的值,例如

func find(obj interface{}, key string) (interface{}, bool) {
    //if the argument is not a map, ignore it
    mobj, ok := obj.(map[string]interface{})
    if !ok {
        return nil, false
    }

    for k, v := range mobj {
        //key match, return value
        if k == key {
            return v, true
        }

        //if the value is a map, search recursively
        if m, ok := v.(map[string]interface{}); ok {
            if res, ok := find(m, key); ok {
                return res, true
            }
        }
        //if the value is an array, search recursively 
        //from each element
        if va, ok := v.([]interface{}); ok {
            for _, a := range va {
                if res, ok := find(a, key); ok {
                    return res,true
                }
            }
        }
    }

    //element not found
    return nil,false
}

注意,上面的函数返回一个interface{}。您需要将其转换为适当的类型,例如使用类型开关:

if ip, ok := find(obj, "ip"); ok {
    switch v := ip.(type) {
    case string:
        fmt.Printf("IP is a string -> %s\n", v)
    case fmt.Stringer:
        fmt.Printf("IP implements stringer interface -> %s\n", v.String())
    case int:

    default:
        fmt.Printf("IP = %v, ok = %v\n", ip, ok)
    }
}

可以在https://play.golang.org/p/O5NUi4J0iR找到一个工作示例

【讨论】:

    【解决方案3】:

    您可以编写自己的解码器或使用现有的第三方解码器。 例如,github.com/buger/jsonparser 可以通过迭代 throw 数组(两次)来解决您的问题。

    package main
    
    import (
        "github.com/buger/jsonparser"
        "fmt"
    )
    
    var data =[]byte(`{
      "type":"example",
      "data":{
        "name":"abc",
        "labels":{
          "key":"value"
        }
      },
      "subsets":[
        {
          "addresses":[
            {
              "ip":"192.168.103.178"
            }
          ],
          "ports":[
            {
              "port":80
            }
          ]
        }
      ]
    }`)
    
    func main() {
        jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
            jsonparser.ArrayEach(value, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
                v, _, _, err := jsonparser.Get(value, "ip")
                if err != nil {
                    return
                }
                fmt.Println("ip: ", string(v[:]))
            }, "addresses")
        }, "subsets")
    }
    

    输出:ip:192.168.103.178

    【讨论】:

    • 对于简单的数据结构不匹配,这似乎是一个不必要的重量级解决方案。
    • @anuribs 数据,_ := ioutil.ReadAll(res.Body)
    【解决方案4】:

    通常在这些情况下,您会看到人们描述所有这些子struct 类型。如果您实际上不需要重用任何子 structs 的定义(例如作为函数参数的类型),那么您不需要定义它们。您可以只对整个响应使用一个定义。另外,在某些情况下你根本不需要定义类型,你可以在声明的时候做:

    package main
    import "encoding/json"
    
    const s = `
    {  
       "subsets": [
          {  
             "addresses": [ 
                {"ip": "192.168.103.178"}
             ]
          }
       ]
    }
    `
    
    func main() {
       var svc struct {
          Subsets []struct {
             Addresses []struct { Ip string }
          }
       }
       json.Unmarshal([]byte(s), &svc)
       ip := svc.Subsets[0].Addresses[0].Ip
       println(ip == "192.168.103.178")
    }
    

    【讨论】:

      猜你喜欢
      • 2020-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-23
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 2018-12-15
      相关资源
      最近更新 更多