Jak uzyskać odpowiedź JSON z http.Get

146

Próbuję odczytać dane JSON z sieci, ale ten kod zwraca pusty wynik. Nie jestem pewien, co tu robię źle.

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
źródło

Odpowiedzi:

283

Idealnym sposobem jest nie używanie ioutil.ReadAll, ale raczej użycie dekodera bezpośrednio na czytniku. Oto fajna funkcja, która pobiera adres URL i dekoduje odpowiedź na targetstrukturę.

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

Przykładowe zastosowanie:

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

Nie powinieneś używać domyślnej *http.Clientstruktury w środowisku produkcyjnym, jak pierwotnie wykazała ta odpowiedź! (Do czego http.Getdzwoni / etc). Powodem jest to, że domyślny klient nie ma ustawionego limitu czasu; jeśli zdalny serwer nie odpowiada, będziesz miał zły dzień.

Connor Peet
źródło
5
Wygląda na to, że musisz używać wielkich liter w nazwach elementów w strukturze, np. type WebKeys struct { Keys []struct { X5t string X5c []string } } Nawet jeśli rzeczywiste parametry w analizowanym pliku JSON są zapisane małymi literami. Przykład JSON:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Wilson
1
@Roman, nie. W przypadku zwrócenia błędu wartość odpowiedzi wynosi zero. (Błąd oznacza, że ​​nie mogliśmy odczytać żadnej prawidłowej odpowiedzi HTTP, nie ma treści do zamknięcia!) Możesz to sprawdzić, wskazując .Get () na nieistniejący adres URL. Ta metoda jest przedstawiona w drugim bloku kodu w net / http docs.
Connor Peet
1
@NamGVU oszczędza potencjalną alokację i pozwala na użycie http keep-alive do ponownego wykorzystania połączeń.
Connor Peet
2
@ConnorPeet Zrobiłeś mój dzień dzięki! Zastanawiam się, co miałeś na myśli, mówiąc „Nie powinieneś używać domyślnej struktury * http.Client w środowisku produkcyjnym”. Czy chodziło Ci o to, że należy używać &http.Client{Timeout: 10 * time.Second}lub używać całej innej biblioteki / strategii?
Jona Rodrigues
8
Tylko ostrzeżenie dla innych - json.NewDecoder(r.Body).Decode(target)będą nie zwróci błąd dla niektórych rodzajów zniekształconego JSON! Po prostu zmarnowałem kilka godzin, próbując zrozumieć, dlaczego ciągle otrzymuję pustą odpowiedź - okazuje się, że źródłowy JSON miał dodatkowy przecinek w miejscu, w którym nie powinien. Proponuję json.Unmarshalzamiast tego użyć . Jest też dobry opis o innych potencjalnych niebezpieczeństwach związanych z używaniem json.Decoder tutaj
adamc
27

Twoim problemem były deklaracje wycinków w twoich danych structs(poza tym Track, że nie powinny to być wycinki ...). Zostało to spotęgowane przez niektóre raczej głupie nazwy pól w pobranym pliku json, które można naprawić za pomocą tagów strukturalnych, patrz godoc .

Poniższy kod pomyślnie przeanalizował plik JSON. Jeśli masz dalsze pytania, daj mi znać.

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()
}
cham
źródło
1
Coś jest w treści odpowiedzi.
peterSO,
6
W moim przypadku brakowało WIELKICH LITER pierwszego znaku w polach „struct”.
abourget
Odpowiedź poniżej jest prawidłowa, używając dekodera bezpośrednio w odpowiedzi.Body unika niepotrzebnych alokacji i generalnie jest bardziej ideomatyczny. Poprawiłem moją odpowiedź, dziękuję za wskazanie tego.
tike
@abourget omg dziękuję za ten komentarz. Po prostu poświęć 1 godzinę na szukanie problemów w parserze, potwierdzając wireshark, że odpowiedź jest poprawna ... dzięki
agilob
14

Potrzebujesz nazw właściwości pisanych dużymi literami w swoich strukturach, aby mogły być używane przez pakiety json.

Nazwy właściwości pisane dużymi literami to exported properties. Nazwy właściwości pisane małymi literami nie są eksportowane.

Musisz również przekazać obiekt danych przez referencję ( &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
źródło
nadal nie działa, czy to działa dla Ciebie? ta sama pusta odpowiedź
Akshaydeep Giri
3
dzięki za "Potrzebujesz nazw właściwości pisanych dużymi literami w swoich strukturach, aby były używane przez pakiety json."
HVNSweeting
9

Wyniki z json.Unmarshal(do var data interface{}) nie pasują bezpośrednio do typu Go i deklaracji zmiennych. Na przykład,

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

Wynik:

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
źródło