Como obter resposta JSON de http.Get

135

Estou tentando ler dados JSON da web, mas esse código retorna resultado vazio. Não tenho certeza do que estou fazendo de errado aqui.

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()
}
Akshaydeep Giri
fonte

Respostas:

266

A maneira ideal é não usar ioutil.ReadAll, mas use um decodificador diretamente no leitor. Aqui está uma função interessante que obtém um URL e decodifica sua resposta em uma targetestrutura.

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)
}

Exemplo de uso:

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)
}

Você não deve usar a *http.Clientestrutura padrão na produção, como esta resposta demonstrou originalmente! (Qual é o que http.Get/ etc chama). O motivo é que o cliente padrão não tem tempo limite definido; se o servidor remoto não responder, você terá um dia ruim.

Connor Peet
fonte
5
Parece que você precisa usar Maiúsculas para os nomes dos itens na estrutura, por exemplo, type WebKeys struct { Keys []struct { X5t string X5c []string } } mesmo quando os parâmetros reais no JSON que você está analisando estão em letras minúsculas. Exemplo de JSON:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Wilson
1
@ Roman, não. Se um erro for retornado, o valor da resposta será nulo. (Um erro significa que não conseguimos ler nenhuma resposta HTTP válida; não há corpo para fechar!) Você pode testar isso apontando .Get () para um URL inexistente. Este método é demonstrado no segundo bloco de código nos documentos net / http .
Connor Peet
1
O @NamGVU salva uma possível alocação e permite o uso de http keep-alive para reutilizar conexões.
Connor Peet
2
@ConnorPeet Você fez meu dia obrigado! Eu me pergunto o que você quis dizer com "Você não deve estar usando a estrutura padrão http.Client * em produção". Você quis dizer que alguém deve usar &http.Client{Timeout: 10 * time.Second}ou usar uma outra biblioteca / estratégia inteira?
Jona Rodrigues
6
Apenas um aviso para os outros - json.NewDecoder(r.Body).Decode(target)vai não retornar um erro para determinados tipos de JSON malformada! Eu perdi algumas horas tentando entender por que continuava recebendo uma resposta vazia - a fonte JSON tinha uma vírgula extra onde não deveria estar. Eu sugiro que você use json.Unmarshal. Há também um bom writeup sobre outros perigos potenciais do uso json.Decoder aqui
adamc
25

Seu problema foram as declarações de fatia nos seus dados structs(exceto Track, não devem ser fatias ...). Isso foi composto por alguns nomes de campo bastante engraçados no arquivo json buscado, que pode ser corrigido via structtags, veja godoc .

O código abaixo analisou o json com êxito. Se tiver mais perguntas, entre em contato.

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()
}
tique
fonte
1
Há algo no corpo da resposta.
peterSO
6
No meu caso, estava faltando o primeiro caractere UPPER-CASE nos campos "struct".
usar o seguinte comando
A resposta abaixo é correta, usando um decodificador diretamente na resposta. O corpo evita alocações desnecessárias e geralmente é mais ideológico. Corrigida minha resposta, obrigado por apontar.
tike
@abourget omg obrigado por este comentário. Basta passar 1 horas à procura de problemas no analisador, confirmando com wireshark que a resposta está correta ... obrigado
agilob
14

Você precisa de nomes de propriedades em maiúsculas em suas estruturas para ser usado pelos pacotes json.

Os nomes de propriedades em maiúsculas são exported properties. Nomes de propriedades em minúsculas não são exportados.

Você também precisa passar o objeto de dados por referência ( &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()
}
Daniel
fonte
ainda não funciona, isso está funcionando para você? mesma resposta em branco
Akshaydeep Giri
3
obrigado por "Você precisa de nomes de propriedades em maiúsculas em suas estruturas para ser usado pelos pacotes json."
usar o seguinte código
8

Os resultados de json.Unmarshal(para var data interface{}) não correspondem diretamente ao seu tipo Go e às declarações de variáveis. Por exemplo,

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()
}

Resultado:

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]]]
peterSO
fonte