Comment obtenir une réponse JSON à partir de http.Get

136

J'essaie de lire les données JSON à partir du Web, mais ce code renvoie un résultat vide. Je ne sais pas ce que je fais de mal ici.

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
la source

Réponses:

267

Le moyen idéal n'est pas d'utiliser ioutil.ReadAll, mais plutôt d'utiliser un décodeur directement sur le lecteur. Voici une fonction intéressante qui récupère une URL et décode sa réponse sur une targetstructure.

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

Exemple d'utilisation:

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

Vous ne devriez pas utiliser la *http.Clientstructure par défaut en production comme cette réponse l'a démontré à l'origine! (C'est ce à quoi http.Get/ etc appelle). La raison en est que le client par défaut n'a pas de délai d'expiration défini; si le serveur distant ne répond pas, vous allez avoir une mauvaise journée.

Connor Peet
la source
5
Il semble que vous deviez utiliser des majuscules pour les noms des éléments de la structure, par exemple, type WebKeys struct { Keys []struct { X5t string X5c []string } } même lorsque les paramètres réels du JSON que vous analysez sont en minuscules. Exemple JSON:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Wilson
1
@Roman, non. Si une erreur est renvoyée, la valeur de la réponse est nulle. (Une erreur signifie que nous n'avons pas pu lire de réponse HTTP valide, il n'y a pas de corps à fermer!) Vous pouvez tester cela en pointant .Get () sur une URL inexistante. Cette méthode est illustrée dans le deuxième bloc de code de la documentation net / http .
Connor Peet
1
@NamGVU enregistre une allocation potentielle et permet l'utilisation de http keep-alive pour réutiliser les connexions.
Connor Peet
2
@ConnorPeet Vous avez rendu ma journée grâce! Je me demande ce que vous vouliez dire par "Vous ne devriez pas utiliser la structure par défaut * http.Client en production". Vouliez-vous dire que l'on devrait utiliser &http.Client{Timeout: 10 * time.Second}ou utiliser une toute autre bibliothèque / stratégie?
Jona Rodrigues
6
Juste un avertissement aux autres - json.NewDecoder(r.Body).Decode(target)ne pas renvoyer une erreur pour certains types de malformé JSON! J'ai juste perdu quelques heures à essayer de comprendre pourquoi je n'arrêtais pas de recevoir une réponse vide - il s'avère que le JSON source avait une virgule supplémentaire là où il n'aurait pas dû être. Je vous suggère d'utiliser à la json.Unmarshalplace. Il y a aussi un bon article sur les autres dangers potentiels de l'utilisation json.Decoder ici
adamc
25

Votre problème était les déclarations de tranches dans vos données structs(sauf Trackqu'elles ne devraient pas être des tranches ...). Cela a été aggravé par des noms de champ plutôt loufoques dans le fichier json récupéré, qui peuvent être corrigés via des structtags, voir godoc .

Le code ci-dessous a analysé le json avec succès. Si vous avez d'autres questions, faites-le moi savoir.

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
la source
1
Il y a quelque chose dans le corps de la réponse.
peterSO
6
Dans mon cas, il me manquait le premier caractère UPPER-CASE dans les champs "struct".
environ
La réponse ci-dessous est juste, en utilisant un décodeur directement sur la réponse. Le corps évite les allocations inutiles et est généralement plus idéomatique. J'ai corrigé ma réponse, merci de l'avoir signalé.
tike
@abourget omg merci pour ce commentaire. Passez juste 1 heure à rechercher des problèmes dans l'analyseur, confirmant avec WireShark que la réponse est correcte ... merci
agilob
14

Vous avez besoin de noms de propriétés en majuscules dans vos structures pour être utilisés par les packages json.

Les noms de propriété en majuscules sont exported properties. Les noms de propriété en minuscules ne sont pas exportés.

Vous devez également transmettre votre objet de données par reference ( &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
la source
ne fonctionne toujours pas, est-ce que cela fonctionne pour vous? même réponse vide
Akshaydeep Giri
3
merci pour "Vous avez besoin de noms de propriétés en majuscules dans vos structures pour être utilisés par les packages json."
HVNSweeting
8

Les résultats de json.Unmarshal(into var data interface{}) ne correspondent pas directement à votre type de Go et à vos déclarations de variables. Par exemple,

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

Production:

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
la source