Come ottenere la risposta JSON da http.Get


136

Sto provando a leggere i dati JSON dal web, ma quel codice restituisce un risultato vuoto. Non sono sicuro di cosa sto facendo di sbagliato qui.

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

Risposte:


267

Il modo ideale non è usare ioutil.ReadAll, ma piuttosto usare direttamente un decodificatore sul lettore. Ecco una bella funzione che ottiene un URL e decodifica la sua risposta su una targetstruttura.

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

Esempio di utilizzo:

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

Non dovresti usare la *http.Clientstruttura predefinita in produzione come questa risposta originariamente dimostrata! (Che è ciò che http.Get/ etc chiama). Il motivo è che il client predefinito non ha alcun timeout impostato; se il server remoto non risponde, avrai una brutta giornata.


5
Sembra che tu debba usare Uppercase per i nomi degli elementi nella struttura, ad esempio type WebKeys struct { Keys []struct { X5t string X5c []string } } anche quando i parametri effettivi in ​​JSON che stai analizzando sono in minuscolo. Esempio JSON:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Wilson,

1
@Roman, no. Se viene restituito un errore, il valore di risposta è zero. (Un errore significa che non siamo riusciti a leggere alcuna risposta HTTP valida, non c'è nessun corpo da chiudere!) Puoi testarlo puntando .Get () su un URL inesistente. Questo metodo è dimostrato nel secondo blocco di codice nei documenti net / http .
Connor Peet,

1
@NamGVU salva una potenziale allocazione e consente l'uso di HTTP keep-alive per riutilizzare le connessioni.
Connor Peet,

2
@ConnorPeet Hai reso la mia giornata grazie! Mi chiedo cosa intendevi con "Non dovresti usare la struttura client predefinita * http. In produzione". Intendevi che si dovrebbe usare &http.Client{Timeout: 10 * time.Second}o usare un'intera altra libreria / strategia?
Jona Rodrigues,

6
Solo un avvertimento per gli altri - json.NewDecoder(r.Body).Decode(target)sarà non restituirà un errore per alcuni tipi di JSON deforme! Ho appena perso qualche ora cercando di capire perché continuavo a ricevere una risposta vuota - risulta che la fonte JSON aveva una virgola in più dove non avrebbe dovuto essere. Ti suggerisco di usare json.Unmarshalinvece. C'è anche un buon resoconto su altri potenziali pericoli dell'utilizzo json.Decoder qui
adamc,

25

Il tuo problema erano le dichiarazioni delle sezioni nei tuoi dati structs(tranne Trackche non dovrebbero essere sezioni ...). Ciò è stato aggravato da alcuni nomi di campo piuttosto sciocchi nel file json recuperato, che può essere risolto tramite structtags, vedi godoc .

Il codice seguente analizza correttamente il json. Se hai ulteriori domande, fammi sapere.

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

1
C'è qualcosa nel corpo della risposta.
peterSO,

6
Nel mio caso, mi mancava il primo carattere UPPER-CASE nei campi "struct".
abourget,

La risposta di seguito è corretta, utilizzando un decodificatore direttamente sulla risposta. Il corpo evita allocazioni non necessarie ed è generalmente più ideomatico. Ho corretto la mia risposta, grazie per averlo sottolineato.
tike

@abourget omg grazie per questo commento. Basta passare 1 ora a cercare problemi nel parser, confermando con WireShark che la risposta è corretta ... grazie
Agilob,

14

Per poter essere utilizzati dai pacchetti json sono necessari nomi di proprietà maiuscoli nelle strutture.

I nomi delle proprietà in maiuscolo sono exported properties. I nomi delle proprietà in minuscolo non vengono esportati.

È inoltre necessario passare l'oggetto dati per riferimento ( &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()
}

non funziona ancora, funziona per te? stessa risposta vuota
Akshaydeep Giri,

3
grazie per "Hai bisogno di nomi di proprietà maiuscoli nelle tue strutture per essere usati dai pacchetti json."
HVN:

8

I risultati da json.Unmarshal(in var data interface{}) non corrispondono direttamente al tipo di Go e alle dichiarazioni delle variabili. Per esempio,

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

Produzione:

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]]]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.