Як отримати відповідь JSON від http.Get


135

Я намагаюся прочитати дані JSON з Інтернету, але цей код повертає порожній результат. Я не впевнений, що я тут роблю неправильно.

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

Відповіді:


266

Ідеальний спосіб - не використовувати ioutil.ReadAll, а скоріше використовувати декодер на читачі. Ось приємна функція, яка отримує URL і декодує її відповідь на targetструктуру.

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

Приклад використання:

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

Ви не повинні використовувати *http.Clientструктуру за замовчуванням у виробництві, як це відповідь спочатку продемонструвало! (Що до чого http.Get/ тощо закликати). Причина полягає в тому, що для клієнта за замовчуванням не встановлено тайм-аут; якщо віддалений сервер не відповідає, у вас буде поганий день.


5
Здається, що вам потрібно використовувати великі регістри для назв елементів у структурі, наприклад, type WebKeys struct { Keys []struct { X5t string X5c []string } } навіть коли фактичні параметри в JSON, який ви розбираєте, знаходяться в малому регістрі. Приклад JSON:{ "keys": [{ "x5t": "foo", "x5c": "baaaar" }] }
Вілсон

1
@ Роман, ні. Якщо помилка повертається, значення відповіді дорівнює нулю. (Помилка означає, що ми не змогли прочитати будь-яку дійсну відповідь HTTP. Закрити тіло немає!) Ви можете перевірити це, вказавши .Get () на неіснуючу URL-адресу. Цей метод демонструється у другому кодовому блоці в документах net / http .
Коннор Піт

1
@NamGVU зберігає потенційне виділення та дозволяє використовувати http підтримку в режимі живого для повторного використання з'єднань.
Коннор Піт

2
@ConnorPeet Ти зробив мій день подяки! Цікаво, що ви мали на увазі під "Ви не повинні використовувати * http.Client структуру за замовчуванням у виробництві". Ви мали на увазі, що слід використовувати &http.Client{Timeout: 10 * time.Second}або використовувати цілу іншу бібліотеку / стратегію?
Jona Rodrigues

6
Просто попередження для інших - json.NewDecoder(r.Body).Decode(target)буде НЕ повертати помилку для деяких типів спотвореної JSON! Я просто витратив кілька годин, намагаючись зрозуміти, чому я продовжував отримувати порожню відповідь - виявляється, у джерела JSON була додаткова кома там, де вона не повинна була бути. Я пропоную використовувати json.Unmarshalзамість цього. Також є хороша інформація про інші потенційні небезпеки використання json.Decoder тут
adamc

25

Вашою проблемою були декларації фрагментів у ваших даних structs(за винятком випадків Track, коли вони не повинні бути фрагментами ...). Це ускладнювалося деякими досить густими іменами полів у добутому файлі json, який можна виправити за допомогою структурних тегів, див. Godoc .

Код нижче успішно проаналізував json. Якщо у вас є додаткові запитання, дайте мені знати.

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
Є щось в тілі реагування.
peterSO

6
У моєму випадку мені не вистачало першого символу UPPER-CASE у полях "struct".
аборт

Відповідь нижче правильна, використовуючи Декодер безпосередньо у відповіді. Тіло уникає зайвих виділень і, як правило, більш ідеоматичне. Виправив мою відповідь, дякую, що вказав на неї.
тик

@abourget omg дякую за цей коментар. Просто витратьте 1 годину на пошуки проблем у синтаксичному
аналізі

14

Для використання пакетами json потрібні імена верхнього регістру у ваших структурах.

Назви властивостей верхнього регістру є exported properties . Імена властивостей малих регістрів не експортуються.

Вам також потрібно передати об’єкт даних за посиланням ( &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()
}

все ще не працює, це працює для вас?
така

3
дякую за "Вам потрібні імена властивостей верхнього регістру у вашій структурі, щоб використовувати пакети json."
HVNSweeting

8

Результати з json.Unmarshalvar data interface{}) прямо не відповідають вашим оголошенням типу Go і змінних. Наприклад,

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

Вихід:

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]]]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.