วิธีรับการตอบสนอง JSON จาก http.Get


137

ฉันพยายามอ่านข้อมูล 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()
}

คำตอบ:


267

วิธีที่ดีที่สุดคือไม่ใช้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" }] }
Wilson

1
@ โรมันไม่มี. หากข้อผิดพลาดถูกส่งกลับค่าตอบกลับจะเป็นศูนย์ (ข้อผิดพลาดหมายความว่าเราไม่สามารถอ่านการตอบสนอง HTTP ที่ถูกต้องไม่มีเนื้อหาให้ปิด!) คุณสามารถทดสอบได้โดยชี้. Get () ที่ URL ที่ไม่มีอยู่จริง วิธี Tis จะแสดงให้เห็นในรหัสบล็อกที่สองในสุทธิ / httpเอกสาร
Connor Peet

1
@NamGVU บันทึกการจัดสรรที่เป็นไปได้และอนุญาตให้ใช้ http keep-alive เพื่อใช้การเชื่อมต่อซ้ำ
Connor Peet

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

26

ปัญหาของคุณคือการประกาศชิ้นส่วนในข้อมูลของคุณstructs(ยกเว้นTrackไม่ควรเป็นชิ้นส่วน ... ) นี้ถูกประกอบโดย fieldnames ค่อนข้างโง่บางส่วนในไฟล์ JSON ลึกซึ้งซึ่งสามารถแก้ไขได้ผ่านทาง structtags ดู 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"
ถึง

คำตอบด้านล่างนี้ถูกต้องโดยใช้ตัวถอดรหัสโดยตรงในการตอบสนองร่างกายหลีกเลี่ยงการจัดสรรที่ไม่จำเป็นและโดยทั่วไปแล้วจะมีอุดมการณ์มากกว่า แก้ไขคำตอบของฉันขอบคุณที่ชี้ให้เห็น
tike

@abourget omg ขอบคุณสำหรับความคิดเห็นนี้ เพียงใช้เวลา 1 ชั่วโมงในการค้นหาปัญหาในการแยกวิเคราะห์ยืนยันกับ wirehark ว่าการตอบสนองถูกต้อง ... ขอบคุณ
agilob

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

ยังไม่ได้ผลสิ่งนี้ใช้ได้กับคุณหรือไม่ คำตอบที่ว่างเปล่าเหมือนกัน
Akshaydeep Giri

3
ขอบคุณสำหรับ "คุณต้องการชื่อคุณสมบัติตัวพิมพ์ใหญ่ในโครงสร้างของคุณเพื่อที่จะใช้โดยแพ็คเกจ json"
HVNSweeting

8

ผลลัพธ์จากjson.Unmarshal(เป็นvar 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]]]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.