So erhalten Sie eine JSON-Antwort von http.Get

135

Ich versuche, JSON-Daten aus dem Web zu lesen, aber dieser Code gibt ein leeres Ergebnis zurück. Ich bin mir nicht sicher, was ich hier falsch mache.

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
quelle

Antworten:

266

Der ideale Weg ist, nicht zu verwenden ioutil.ReadAll, sondern einen Decoder direkt am Lesegerät zu verwenden. Hier ist eine nette Funktion, die eine URL erhält und ihre Antwort auf eine targetStruktur dekodiert .

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

Anwendungsbeispiel:

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

Sie sollten nicht die Standardstruktur *http.Clientin der Produktion verwenden, wie diese Antwort ursprünglich gezeigt hat! (Welches ist, was http.Get/ etc anrufen). Der Grund dafür ist, dass für den Standardclient kein Timeout festgelegt wurde. Wenn der Remote-Server nicht reagiert, haben Sie einen schlechten Tag.

Connor Peet
quelle
5
Es scheint, als müssten Sie Großbuchstaben für die Namen der Elemente in der Struktur verwenden, z. B. type WebKeys struct { Keys []struct { X5t string X5c []string } } selbst wenn die tatsächlichen Parameter in dem JSON, den Sie analysieren, in Kleinbuchstaben geschrieben sind. JSON Beispiel:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Wilson
1
@ Roman, nein. Wenn ein Fehler zurückgegeben wird, ist der Antwortwert Null. (Ein Fehler bedeutet, dass wir keine gültige HTTP-Antwort lesen konnten. Es gibt keinen zu schließenden Text!) Sie können dies testen, indem Sie .Get () auf eine nicht vorhandene URL verweisen. Diese Methode wird im zweiten Codeblock in den net / http- Dokumenten demonstriert .
Connor Peet
1
@NamGVU speichert eine potenzielle Zuordnung und ermöglicht die Verwendung von http Keep-Alive zur Wiederverwendung von Verbindungen.
Connor Peet
2
@ConnorPeet Du hast meinen Tag gemacht, danke! Ich frage mich, was Sie mit "Sie sollten nicht die Standardstruktur * http.Client in der Produktion verwenden" gemeint haben. Meinten Sie, dass man &http.Client{Timeout: 10 * time.Second}eine ganz andere Bibliothek / Strategie verwenden oder verwenden sollte ?
Jona Rodrigues
6
Nur eine Warnung an andere - json.NewDecoder(r.Body).Decode(target)gibt keinen Fehler für bestimmte Arten von fehlerhaftem JSON zurück! Ich habe nur ein paar Stunden damit verbracht zu verstehen, warum ich immer wieder eine leere Antwort erhielt - es stellte sich heraus, dass die Quelle JSON ein zusätzliches Komma hatte, wo es nicht hätte sein sollen. Ich schlage vor, Sie verwenden json.Unmarshalstattdessen. Es gibt auch eine gute Zusammenfassung über andere mögliche Gefahren der Verwendung json.Decoder hier
Adamc
25

Ihr Problem waren die Slice-Deklarationen in Ihren Daten structs(außer Track, sie sollten keine Slices sein ...). Dies wurde durch einige ziemlich doofe Feldnamen in der abgerufenen json-Datei verstärkt, die über Strukturtags behoben werden können, siehe godoc .

Der folgende Code hat den JSON erfolgreich analysiert. Wenn Sie weitere Fragen haben, lassen Sie es mich wissen.

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()
}
tike
quelle
1
Es gibt etwas im Antwortkörper.
PeterSO
6
In meinem Fall fehlte mir das erste UPPER-CASE-Zeichen in den Feldern "struct".
Abourget
Die Antwort unten ist richtig, wenn Sie einen Decoder direkt für die Antwort verwenden. Der Körper vermeidet unnötige Zuweisungen und ist im Allgemeinen ideomatischer. Ich habe meine Antwort korrigiert, danke, dass Sie darauf hingewiesen haben.
Tike
@abourget omg danke für diesen Kommentar. Verbringen Sie einfach 1 Stunde damit, nach Problemen im Parser zu suchen und mit wireshark zu bestätigen, dass die Antwort korrekt ist ... danke
agilob
14

Sie benötigen Eigenschaftsnamen in Großbuchstaben in Ihren Strukturen, um von den json-Paketen verwendet zu werden.

Eigenschaftsnamen in Großbuchstaben sind exported properties. Eigenschaftsnamen in Kleinbuchstaben werden nicht exportiert.

Sie müssen das Datenobjekt auch als Referenz übergeben ( &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
quelle
funktioniert immer noch nicht, funktioniert das für dich? gleiche leere Antwort
Akshaydeep Giri
3
danke für "Sie benötigen Eigenschaftsnamen in Großbuchstaben in Ihren Strukturen, um von den json-Paketen verwendet zu werden."
HVNSweeting
8

Die Ergebnisse von json.Unmarshal(in var data interface{}) stimmen nicht direkt mit Ihren Go-Typ- und Variablendeklarationen überein. Beispielsweise,

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

Ausgabe:

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
quelle