【问题标题】:How to get JSON response from http.Get如何从 http.Get 获取 JSON 响应
【发布时间】:2013-06-17 20:35:40
【问题描述】:

我正在尝试从 Web 读取 JSON 数据,但该代码返回空结果。我不确定我在这里做错了什么。

package main

import "os"
import "fmt"
import "net/http"
import "io/ioutil"
import "encoding/json"

type Tracks struct {
    Toptracks []Toptracks_info
}

type Toptracks_info struct {
    Track []Track_info
    Attr  []Attr_info
}

type Track_info struct {
    Name       string
    Duration   string
    Listeners  string
    Mbid       string
    Url        string
    Streamable []Streamable_info
    Artist     []Artist_info
    Attr       []Track_attr_info
}

type Attr_info struct {
    Country    string
    Page       string
    PerPage    string
    TotalPages string
    Total      string
}

type Streamable_info struct {
    Text      string
    Fulltrack string
}

type Artist_info struct {
    Name string
    Mbid string
    Url  string
}

type Track_attr_info struct {
    Rank string
}

func get_content() {
    // json data
    url := "http://ws.audioscrobbler.com/2.0/?method=geo.gettoptracks&api_key=c1572082105bd40d247836b5c1819623&format=json&country=Netherlands"

    res, err := http.Get(url)

    if err != nil {
        panic(err.Error())
    }

    body, err := ioutil.ReadAll(res.Body)

    if err != nil {
        panic(err.Error())
    }

    var data Tracks
    json.Unmarshal(body, &data)
    fmt.Printf("Results: %v\n", data)
    os.Exit(0)
}

func main() {
    get_content()
}

【问题讨论】:

    标签: json go


    【解决方案1】:

    理想的方式是使用ioutil.ReadAll,而是直接在阅读器上使用解码器。这是一个很好的函数,它获取一个 url 并将其响应解码为 target 结构。

    var myClient = &http.Client{Timeout: 10 * time.Second}
    
    func getJson(url string, target interface{}) error {
        r, err := myClient.Get(url)
        if err != nil {
            return err
        }
        defer r.Body.Close()
    
        return json.NewDecoder(r.Body).Decode(target)
    }
    

    使用示例:

    type Foo struct {
        Bar string
    }
    
    func main() {
        foo1 := new(Foo) // or &Foo{}
        getJson("http://example.com", foo1)
        println(foo1.Bar)
    
        // alternately:
    
        foo2 := Foo{}
        getJson("http://example.com", &foo2)
        println(foo2.Bar)
    }
    

    您不应该在生产中使用默认的*http.Client 结构,因为这个答案最初证明了! (这是http.Get/etc 调用的)。原因是默认客户端没有设置超时;如果远程服务器没有响应,那你的日子就不好过了。

    【讨论】:

    • 似乎你需要使用大写作为结构中项目的名称,例如type WebKeys struct { Keys []struct { X5t string X5c []string } } 即使您正在解析的 JSON 中的实际参数是小写的。 JSON 示例:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
    • @Roman,没有。如果返回错误,则响应值为 nil。 (错误意味着我们无法读取任何有效的 HTTP 响应,没有要关闭的正文!)您可以通过将 .Get() 指向不存在的 URL 来测试它。这种方法在net/http 文档的第二个代码块中进行了演示。
    • @NamGVU 保存了潜在的分配并允许使用 http keep-alive 来重用连接。
    • @ConnorPeet 你让我开心,谢谢!我想知道“您不应该在生产中使用默认的 *http.Client 结构”是什么意思。您的意思是应该使用&http.Client{Timeout: 10 * time.Second} 还是使用整个其他库/策略?
    • 只是对其他人的警告 - json.NewDecoder(r.Body).Decode(target)不会为某些类型的格式错误的 JSON 返回错误!我只是浪费了几个小时试图理解为什么我总是得到一个空响应 - 结果源 JSON 在不应该出现的地方有一个额外的逗号。我建议你改用json.Unmarshal。还有一篇关于使用 json.Decoder here 的其他潜在危险的好文章
    【解决方案2】:

    您的问题是数据structs 中的切片声明(Track 除外,它们不应该是切片...)。获取的 json 文件中的一些相当愚蠢的字段名加剧了这种情况,这些字段名可以通过 structtags 进行修复,请参阅godoc

    下面的代码成功解析了 json。如果您还有其他问题,请告诉我。

    package main
    
    import "fmt"
    import "net/http"
    import "io/ioutil"
    import "encoding/json"
    
    type Tracks struct {
        Toptracks Toptracks_info
    }
    
    type Toptracks_info struct {
        Track []Track_info
        Attr  Attr_info `json: "@attr"`
    }
    
    type Track_info struct {
        Name       string
        Duration   string
        Listeners  string
        Mbid       string
        Url        string
        Streamable Streamable_info
        Artist     Artist_info   
        Attr       Track_attr_info `json: "@attr"`
    }
    
    type Attr_info struct {
        Country    string
        Page       string
        PerPage    string
        TotalPages string
        Total      string
    }
    
    type Streamable_info struct {
        Text      string `json: "#text"`
        Fulltrack string
    }
    
    type Artist_info struct {
        Name string
        Mbid string
        Url  string
    }
    
    type Track_attr_info struct {
        Rank string
    }
    
    func perror(err error) {
        if err != nil {
            panic(err)
        }
    }
    
    func get_content() {
        url := "http://ws.audioscrobbler.com/2.0/?method=geo.gettoptracks&api_key=c1572082105bd40d247836b5c1819623&format=json&country=Netherlands"
    
        res, err := http.Get(url)
        perror(err)
        defer res.Body.Close()
    
        decoder := json.NewDecoder(res.Body)
        var data Tracks
        err = decoder.Decode(&data)
        if err != nil {
            fmt.Printf("%T\n%s\n%#v\n",err, err, err)
            switch v := err.(type){
                case *json.SyntaxError:
                    fmt.Println(string(body[v.Offset-40:v.Offset]))
            }
        }
        for i, track := range data.Toptracks.Track{
            fmt.Printf("%d: %s %s\n", i, track.Artist.Name, track.Name)
        }
    }
    
    func main() {
        get_content()
    }
    

    【讨论】:

    • 响应正文中有内​​容。
    • 就我而言,我在“结构”字段中缺少大写第一个字符。
    • 下面的答案是对的,直接在response上使用一个Decoder。Body避免了不必要的分配,一般来说更符合理念。更正了我的答案,谢谢指出。
    • @abourget 天哪,谢谢你的评论。只需花 1 小时寻找解析器中的问题,用wireshark 确认响应是正确的......谢谢
    【解决方案3】:

    您的结构中的属性名称需要大写才能被 json 包使用。

    大写的属性名称是exported properties。不会导出小写的属性名称。

    您还需要通过引用 (&data) 传递您的数据对象。

    package main
    
    import "os"
    import "fmt"
    import "net/http"
    import "io/ioutil"
    import "encoding/json"
    
    type tracks struct {
        Toptracks []toptracks_info
    }
    
    type toptracks_info struct {
        Track []track_info
        Attr  []attr_info
    }
    
    type track_info struct {
        Name       string
        Duration   string
        Listeners  string
        Mbid       string
        Url        string
        Streamable []streamable_info
        Artist     []artist_info
        Attr       []track_attr_info
    }
    
    type attr_info struct {
        Country    string
        Page       string
        PerPage    string
        TotalPages string
        Total      string
    }
    
    type streamable_info struct {
        Text      string
        Fulltrack string
    }
    
    type artist_info struct {
        Name string
        Mbid string
        Url  string
    }
    
    type track_attr_info struct {
        Rank string
    }
    
    func get_content() {
        // json data
        url := "http://ws.audioscrobbler.com/2.0/?method=geo.gettoptracks&api_key=c1572082105bd40d247836b5c1819623&format=json&country=Netherlands"
    
        res, err := http.Get(url)
    
        if err != nil {
            panic(err.Error())
        }
    
        body, err := ioutil.ReadAll(res.Body)
    
        if err != nil {
            panic(err.Error())
        }
    
        var data tracks
        json.Unmarshal(body, &data)
        fmt.Printf("Results: %v\n", data)
        os.Exit(0)
    }
    
    func main() {
        get_content()
    }
    

    【讨论】:

    • 还是不行,这对你有用吗?相同的空响应
    • 感谢“您需要在结构中使用大写的属性名称才能被 json 包使用。”
    【解决方案4】:

    json.Unmarshal(到var data interface{})的结果与您的 Go 类型和变量声明不直接匹配。例如,

    package main
    
    import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "net/http"
        "os"
    )
    
    type Tracks struct {
        Toptracks []Toptracks_info
    }
    
    type Toptracks_info struct {
        Track []Track_info
        Attr  []Attr_info
    }
    
    type Track_info struct {
        Name       string
        Duration   string
        Listeners  string
        Mbid       string
        Url        string
        Streamable []Streamable_info
        Artist     []Artist_info
        Attr       []Track_attr_info
    }
    
    type Attr_info struct {
        Country    string
        Page       string
        PerPage    string
        TotalPages string
        Total      string
    }
    
    type Streamable_info struct {
        Text      string
        Fulltrack string
    }
    
    type Artist_info struct {
        Name string
        Mbid string
        Url  string
    }
    
    type Track_attr_info struct {
        Rank string
    }
    
    func get_content() {
        // json data
        url := "http://ws.audioscrobbler.com/2.0/?method=geo.gettoptracks&api_key=c1572082105bd40d247836b5c1819623&format=json&country=Netherlands"
        url += "&limit=1" // limit data for testing
        res, err := http.Get(url)
        if err != nil {
            panic(err.Error())
        }
        body, err := ioutil.ReadAll(res.Body)
        if err != nil {
            panic(err.Error())
        }
        var data interface{} // TopTracks
        err = json.Unmarshal(body, &data)
        if err != nil {
            panic(err.Error())
        }
        fmt.Printf("Results: %v\n", data)
        os.Exit(0)
    }
    
    func main() {
        get_content()
    }
    

    输出:

    Results: map[toptracks:map[track:map[name:Get Lucky (feat. Pharrell Williams) listeners:1863 url:http://www.last.fm/music/Daft+Punk/_/Get+Lucky+(feat.+Pharrell+Williams) artist:map[name:Daft Punk mbid:056e4f3e-d505-4dad-8ec1-d04f521cbb56 url:http://www.last.fm/music/Daft+Punk] image:[map[#text:http://userserve-ak.last.fm/serve/34s/88137413.png size:small] map[#text:http://userserve-ak.last.fm/serve/64s/88137413.png size:medium] map[#text:http://userserve-ak.last.fm/serve/126/88137413.png size:large] map[#text:http://userserve-ak.last.fm/serve/300x300/88137413.png size:extralarge]] @attr:map[rank:1] duration:369 mbid: streamable:map[#text:1 fulltrack:0]] @attr:map[country:Netherlands page:1 perPage:1 totalPages:500 total:500]]]
    

    【讨论】:

      【解决方案5】:

      好吧,伙计们,我在这上面浪费了一些时间,因为上述解决方案都不适合我。

      1. 那么对我来说,问题是如上面第一个结构中的示例“Toptracks”中所述,我将其重命名为“Tracks”以匹配第一层。
      2. 其他问题出现在结构“Toptracks_info”中,属性为“Attr”,这里的问题是在响应中我们得到“@attr”并且您不能输入@,因为它是用于命名属性的无效字符,所以我必须添加@ 987654321@(图像数组中的“#text”也是如此 所以例子是:

      主要是

      type Tracks struct {
          Tracks Toptracks_info
      }
      
      type Toptracks_info struct {
          Track []Track_info
          Attr  Attr_info `json:"@attr"`
      }
      
      type Track_info struct {
          Name       string
          Duration   string
          Listeners  string
          Mbid       string
          Url        string
          Streamable Streamable_info
          Artist     Artist_info
          Image      []Image
          Attr       Track_attr_info
      }
      type Image struct {
          Text string `json:"#text"`
          Size string
      }
      
      type Attr_info struct {
          Country    string
          Page       string
          PerPage    string
          TotalPages string
          Total      string
      }
      
      type Streamable_info struct {
          Text      string
          Fulltrack string
      }
      
      type Artist_info struct {
          Name string
          Mbid string
          Url  string
      }
      
      type Track_attr_info struct {
          Rank string
      }
      
      
      url := "http://ws.audioscrobbler.com/2.0/?method=geo.gettoptracks&api_key=xxxxxxxx&format=json&country=Netherlands"
      
      res, err := http.Get(url)
      
      body, err := ioutil.ReadAll(res.Body)
      var data Tracks
      json.Unmarshal(body, &data)
      
      if err != nil {
          panic(err.Error())
      }
      

      这是我第一次使用 Go,这让我有点发疯。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-01-24
        • 2022-06-28
        • 2018-02-25
        • 2017-09-26
        • 2020-08-12
        • 2020-10-30
        • 2019-04-25
        相关资源
        最近更新 更多