Cómo obtener una respuesta JSON de http.Get

135

Estoy tratando de leer datos JSON de la web, pero ese código devuelve un resultado vacío. No estoy seguro de qué estoy haciendo mal aquí.

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
fuente

Respuestas:

266

La forma ideal es no usar ioutil.ReadAll, sino usar un decodificador en el lector directamente. Aquí hay una buena función que obtiene una url y decodifica su respuesta en una targetestructura.

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

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

¡No debería usar la *http.Clientestructura predeterminada en producción como esta respuesta demostró originalmente! (Que es a lo que http.Get/ etc llama). La razón es que el cliente predeterminado no tiene establecido un tiempo de espera; Si el servidor remoto no responde, tendrá un mal día.

Connor Peet
fuente
55
Parece que necesita usar mayúsculas para los nombres de los elementos en la estructura, por ejemplo, type WebKeys struct { Keys []struct { X5t string X5c []string } } incluso cuando los parámetros reales en el JSON que está analizando están en minúsculas. Ejemplo de JSON:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Wilson
1
@Roman, no. Si se devuelve un error, el valor de respuesta es nulo. (Un error significa que no pudimos leer ninguna respuesta HTTP válida, ¡no hay ningún cuerpo para cerrar!) Puede probar esto señalando .Get () en una URL inexistente. Este método se demuestra en el segundo bloque de código en los documentos net / http .
Connor Peet
1
@NamGVU guarda una asignación potencial y permite el uso de http keep-alive para reutilizar las conexiones.
Connor Peet
2
@ConnorPeet Me alegraste el día, gracias! Me pregunto qué quisiste decir con "No deberías usar la estructura de cliente http * predeterminada en producción". ¿Quiso decir que uno debería usar &http.Client{Timeout: 10 * time.Second}o usar otra biblioteca / estrategia?
Jona Rodrigues
66
Sólo una advertencia a los demás - json.NewDecoder(r.Body).Decode(target)se no devolverá un error para ciertos tipos de JSON malformación! Simplemente perdí unas horas tratando de entender por qué seguía recibiendo una respuesta vacía; resulta que la fuente JSON tenía una coma adicional donde no debería haber estado. Te sugiero que uses json.Unmarshalen su lugar. También hay una buena reseña sobre otros peligros potenciales de usar json.Decoder aquí
adamc
25

Su problema fueron las declaraciones de sectores en sus datos structs(excepto Trackque no deberían ser sectores ...). Esto se vio agravado por algunos nombres de campo bastante tontos en el archivo json recuperado, que se puede arreglar a través de estructuras, ver godoc .

El siguiente código analizó el json con éxito. Si tiene más preguntas, hágamelo saber.

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
fuente
1
Hay algo en el cuerpo de respuesta.
peterSO
66
En mi caso, me faltaba el primer carácter MAYÚSCULAS en los campos "estructura".
Abourget
La respuesta a continuación es correcta, utilizando un decodificador directamente en la respuesta. El cuerpo evita asignaciones innecesarias y generalmente es más ideomático. Corregí mi respuesta, gracias por señalarlo.
tike
@abourget omg gracias por este comentario. Simplemente pase 1 hora buscando problemas en el analizador, confirmando con wireshark que la respuesta es correcta ... gracias
agilob
14

Necesita nombres de propiedad en mayúsculas en sus estructuras para ser utilizados por los paquetes json.

Los nombres de propiedad en mayúsculas son exported properties. Los nombres de propiedad en minúsculas no se exportan.

También debe pasar el objeto de datos por referencia ( &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
fuente
todavía no funciona, ¿está funcionando para ti? misma respuesta vacía
Akshaydeep Giri
3
gracias por "Necesita nombres de propiedad en mayúsculas en sus estructuras para que los paquetes json los usen".
HVNSweeting
8

Los resultados de json.Unmarshal(into var data interface{}) no coinciden directamente con su tipo de Go y las declaraciones de variables. Por ejemplo,

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

Salida:

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
fuente