99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Go 語(yǔ)言 JSON

2023-03-14 16:52 更新

原文鏈接:https://gopl-zh.github.io/ch4/ch4-05.html


4.5. JSON

JavaScript對(duì)象表示法(JSON)是一種用于發(fā)送和接收結(jié)構(gòu)化信息的標(biāo)準(zhǔn)協(xié)議。在類(lèi)似的協(xié)議中,JSON并不是唯一的一個(gè)標(biāo)準(zhǔn)協(xié)議。 XML(§7.14)、ASN.1和Google的Protocol Buffers都是類(lèi)似的協(xié)議,并且有各自的特色,但是由于簡(jiǎn)潔性、可讀性和流行程度等原因,JSON是應(yīng)用最廣泛的一個(gè)。

Go語(yǔ)言對(duì)于這些標(biāo)準(zhǔn)格式的編碼和解碼都有良好的支持,由標(biāo)準(zhǔn)庫(kù)中的encoding/json、encoding/xml、encoding/asn1等包提供支持(譯注:Protocol Buffers的支持由 github.com/golang/protobuf 包提供),并且這類(lèi)包都有著相似的API接口。本節(jié),我們將對(duì)重要的encoding/json包的用法做個(gè)概述。

JSON是對(duì)JavaScript中各種類(lèi)型的值——字符串、數(shù)字、布爾值和對(duì)象——Unicode本文編碼。它可以用有效可讀的方式表示第三章的基礎(chǔ)數(shù)據(jù)類(lèi)型和本章的數(shù)組、slice、結(jié)構(gòu)體和map等聚合數(shù)據(jù)類(lèi)型。

基本的JSON類(lèi)型有數(shù)字(十進(jìn)制或科學(xué)記數(shù)法)、布爾值(true或false)、字符串,其中字符串是以雙引號(hào)包含的Unicode字符序列,支持和Go語(yǔ)言類(lèi)似的反斜杠轉(zhuǎn)義特性,不過(guò)JSON使用的是\Uhhhh轉(zhuǎn)義數(shù)字來(lái)表示一個(gè)UTF-16編碼(譯注:UTF-16和UTF-8一樣是一種變長(zhǎng)的編碼,有些Unicode碼點(diǎn)較大的字符需要用4個(gè)字節(jié)表示;而且UTF-16還有大端和小端的問(wèn)題),而不是Go語(yǔ)言的rune類(lèi)型。

這些基礎(chǔ)類(lèi)型可以通過(guò)JSON的數(shù)組和對(duì)象類(lèi)型進(jìn)行遞歸組合。一個(gè)JSON數(shù)組是一個(gè)有序的值序列,寫(xiě)在一個(gè)方括號(hào)中并以逗號(hào)分隔;一個(gè)JSON數(shù)組可以用于編碼Go語(yǔ)言的數(shù)組和slice。一個(gè)JSON對(duì)象是一個(gè)字符串到值的映射,寫(xiě)成一系列的name:value對(duì)形式,用花括號(hào)包含并以逗號(hào)分隔;JSON的對(duì)象類(lèi)型可以用于編碼Go語(yǔ)言的map類(lèi)型(key類(lèi)型是字符串)和結(jié)構(gòu)體。例如:

boolean         true
number          -273.15
string          "She said \"Hello, BF\""
array           ["gold", "silver", "bronze"]
object          {"year": 1980,
                 "event": "archery",
                 "medals": ["gold", "silver", "bronze"]}

考慮一個(gè)應(yīng)用程序,該程序負(fù)責(zé)收集各種電影評(píng)論并提供反饋功能。它的Movie數(shù)據(jù)類(lèi)型和一個(gè)典型的表示電影的值列表如下所示。(在結(jié)構(gòu)體聲明中,Year和Color成員后面的字符串面值是結(jié)構(gòu)體成員Tag;我們稍后會(huì)解釋它的作用。)

gopl.io/ch4/movie

type Movie struct {
    Title  string
    Year   int  `json:"released"`
    Color  bool `json:"color,omitempty"`
    Actors []string
}

var movies = []Movie{
    {Title: "Casablanca", Year: 1942, Color: false,
        Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
    {Title: "Cool Hand Luke", Year: 1967, Color: true,
        Actors: []string{"Paul Newman"}},
    {Title: "Bullitt", Year: 1968, Color: true,
        Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
    // ...
}

這樣的數(shù)據(jù)結(jié)構(gòu)特別適合JSON格式,并且在兩者之間相互轉(zhuǎn)換也很容易。將一個(gè)Go語(yǔ)言中類(lèi)似movies的結(jié)構(gòu)體slice轉(zhuǎn)為JSON的過(guò)程叫編組(marshaling)。編組通過(guò)調(diào)用json.Marshal函數(shù)完成:

data, err := json.Marshal(movies)
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

Marshal函數(shù)返回一個(gè)編碼后的字節(jié)slice,包含很長(zhǎng)的字符串,并且沒(méi)有空白縮進(jìn);我們將它折行以便于顯示:

[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]

這種緊湊的表示形式雖然包含了全部的信息,但是很難閱讀。為了生成便于閱讀的格式,另一個(gè)json.MarshalIndent函數(shù)將產(chǎn)生整齊縮進(jìn)的輸出。該函數(shù)有兩個(gè)額外的字符串參數(shù)用于表示每一行輸出的前綴和每一個(gè)層級(jí)的縮進(jìn):

data, err := json.MarshalIndent(movies, "", "    ")
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

上面的代碼將產(chǎn)生這樣的輸出(譯注:在最后一個(gè)成員或元素后面并沒(méi)有逗號(hào)分隔符):

[
    {
        "Title": "Casablanca",
        "released": 1942,
        "Actors": [
            "Humphrey Bogart",
            "Ingrid Bergman"
        ]
    },
    {
        "Title": "Cool Hand Luke",
        "released": 1967,
        "color": true,
        "Actors": [
            "Paul Newman"
        ]
    },
    {
        "Title": "Bullitt",
        "released": 1968,
        "color": true,
        "Actors": [
            "Steve McQueen",
            "Jacqueline Bisset"
        ]
    }
]

在編碼時(shí),默認(rèn)使用Go語(yǔ)言結(jié)構(gòu)體的成員名字作為JSON的對(duì)象(通過(guò)reflect反射技術(shù),我們將在12.6節(jié)討論)。只有導(dǎo)出的結(jié)構(gòu)體成員才會(huì)被編碼,這也就是我們?yōu)槭裁催x擇用大寫(xiě)字母開(kāi)頭的成員名稱(chēng)。

細(xì)心的讀者可能已經(jīng)注意到,其中Year名字的成員在編碼后變成了released,還有Color成員編碼后變成了小寫(xiě)字母開(kāi)頭的color。這是因?yàn)榻Y(jié)構(gòu)體成員Tag所導(dǎo)致的。一個(gè)結(jié)構(gòu)體成員Tag是和在編譯階段關(guān)聯(lián)到該成員的元信息字符串:

Year  int  `json:"released"`
Color bool `json:"color,omitempty"`

結(jié)構(gòu)體的成員Tag可以是任意的字符串面值,但是通常是一系列用空格分隔的key:"value"鍵值對(duì)序列;因?yàn)橹抵泻须p引號(hào)字符,因此成員Tag一般用原生字符串面值的形式書(shū)寫(xiě)。json開(kāi)頭鍵名對(duì)應(yīng)的值用于控制encoding/json包的編碼和解碼的行為,并且encoding/...下面其它的包也遵循這個(gè)約定。成員Tag中json對(duì)應(yīng)值的第一部分用于指定JSON對(duì)象的名字,比如將Go語(yǔ)言中的TotalCount成員對(duì)應(yīng)到JSON中的total_count對(duì)象。Color成員的Tag還帶了一個(gè)額外的omitempty選項(xiàng),表示當(dāng)Go語(yǔ)言結(jié)構(gòu)體成員為空或零值時(shí)不生成該JSON對(duì)象(這里false為零值)。果然,Casablanca是一個(gè)黑白電影,并沒(méi)有輸出Color成員。

編碼的逆操作是解碼,對(duì)應(yīng)將JSON數(shù)據(jù)解碼為Go語(yǔ)言的數(shù)據(jù)結(jié)構(gòu),Go語(yǔ)言中一般叫unmarshaling,通過(guò)json.Unmarshal函數(shù)完成。下面的代碼將JSON格式的電影數(shù)據(jù)解碼為一個(gè)結(jié)構(gòu)體slice,結(jié)構(gòu)體中只有Title成員。通過(guò)定義合適的Go語(yǔ)言數(shù)據(jù)結(jié)構(gòu),我們可以選擇性地解碼JSON中感興趣的成員。當(dāng)Unmarshal函數(shù)調(diào)用返回,slice將被只含有Title信息的值填充,其它JSON成員將被忽略。

var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // "[{Casablanca} {Cool Hand Luke} {Bullitt}]"

許多web服務(wù)都提供JSON接口,通過(guò)HTTP接口發(fā)送JSON格式請(qǐng)求并返回JSON格式的信息。為了說(shuō)明這一點(diǎn),我們通過(guò)Github的issue查詢(xún)服務(wù)來(lái)演示類(lèi)似的用法。首先,我們要定義合適的類(lèi)型和常量:

gopl.io/ch4/github

// Package github provides a Go API for the GitHub issue tracker.
// See https://developer.github.com/v3/search/#search-issues.
package github

import "time"

const IssuesURL = "https://api.github.com/search/issues"

type IssuesSearchResult struct {
    TotalCount int `json:"total_count"`
    Items          []*Issue
}

type Issue struct {
    Number    int
    HTMLURL   string `json:"html_url"`
    Title     string
    State     string
    User      *User
    CreatedAt time.Time `json:"created_at"`
    Body      string    // in Markdown format
}

type User struct {
    Login   string
    HTMLURL string `json:"html_url"`
}

和前面一樣,即使對(duì)應(yīng)的JSON對(duì)象名是小寫(xiě)字母,每個(gè)結(jié)構(gòu)體的成員名也是聲明為大寫(xiě)字母開(kāi)頭的。因?yàn)橛行㎎SON成員名字和Go結(jié)構(gòu)體成員名字并不相同,因此需要Go語(yǔ)言結(jié)構(gòu)體成員Tag來(lái)指定對(duì)應(yīng)的JSON名字。同樣,在解碼的時(shí)候也需要做同樣的處理,GitHub服務(wù)返回的信息比我們定義的要多很多。

SearchIssues函數(shù)發(fā)出一個(gè)HTTP請(qǐng)求,然后解碼返回的JSON格式的結(jié)果。因?yàn)橛脩籼峁┑牟樵?xún)條件可能包含類(lèi)似?&之類(lèi)的特殊字符,為了避免對(duì)URL造成沖突,我們用url.QueryEscape來(lái)對(duì)查詢(xún)中的特殊字符進(jìn)行轉(zhuǎn)義操作。

gopl.io/ch4/github

package github

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "strings"
)

// SearchIssues queries the GitHub issue tracker.
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
    q := url.QueryEscape(strings.Join(terms, " "))
    resp, err := http.Get(IssuesURL + "?q=" + q)
    if err != nil {
        return nil, err
    }

    // We must close resp.Body on all execution paths.
    // (Chapter 5 presents 'defer', which makes this simpler.)
    if resp.StatusCode != http.StatusOK {
        resp.Body.Close()
        return nil, fmt.Errorf("search query failed: %s", resp.Status)
    }

    var result IssuesSearchResult
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        resp.Body.Close()
        return nil, err
    }
    resp.Body.Close()
    return &result, nil
}

在早些的例子中,我們使用了json.Unmarshal函數(shù)來(lái)將JSON格式的字符串解碼為字節(jié)slice。但是這個(gè)例子中,我們使用了基于流式的解碼器json.Decoder,它可以從一個(gè)輸入流解碼JSON數(shù)據(jù),盡管這不是必須的。如您所料,還有一個(gè)針對(duì)輸出流的json.Encoder編碼對(duì)象。

我們調(diào)用Decode方法來(lái)填充變量。這里有多種方法可以格式化結(jié)構(gòu)。下面是最簡(jiǎn)單的一種,以一個(gè)固定寬度打印每個(gè)issue,但是在下一節(jié)我們將看到如何利用模板來(lái)輸出復(fù)雜的格式。

gopl.io/ch4/issues

// Issues prints a table of GitHub issues matching the search terms.
package main

import (
    "fmt"
    "log"
    "os"

    "gopl.io/ch4/github"
)

func main() {
    result, err := github.SearchIssues(os.Args[1:])
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%d issues:\n", result.TotalCount)
    for _, item := range result.Items {
        fmt.Printf("#%-5d %9.9s %.55s\n",
            item.Number, item.User.Login, item.Title)
    }
}

通過(guò)命令行參數(shù)指定檢索條件。下面的命令是查詢(xún)Go語(yǔ)言項(xiàng)目中和JSON解碼相關(guān)的問(wèn)題,還有查詢(xún)返回的結(jié)果:

$ go build gopl.io/ch4/issues
$ ./issues repo:golang/go is:open json decoder
13 issues:
#5680    eaigner encoding/json: set key converter on en/decoder
#6050  gopherbot encoding/json: provide tokenizer
#8658  gopherbot encoding/json: use bufio
#8462  kortschak encoding/json: UnmarshalText confuses json.Unmarshal
#5901        rsc encoding/json: allow override type marshaling
#9812  klauspost encoding/json: string tag not symmetric
#7872  extempora encoding/json: Encoder internally buffers full output
#9650    cespare encoding/json: Decoding gives errPhase when unmarshalin
#6716  gopherbot encoding/json: include field name in unmarshal error me
#6901  lukescott encoding/json, encoding/xml: option to treat unknown fi
#6384    joeshaw encoding/json: encode precise floating point integers u
#6647    btracey x/tools/cmd/godoc: display type kind of each named type
#4237  gjemiller encoding/base64: URLEncoding padding is optional

GitHub的Web服務(wù)接口 https://developer.github.com/v3/ 包含了更多的特性。

練習(xí) 4.10: 修改issues程序,根據(jù)問(wèn)題的時(shí)間進(jìn)行分類(lèi),比如不到一個(gè)月的、不到一年的、超過(guò)一年。

練習(xí) 4.11: 編寫(xiě)一個(gè)工具,允許用戶在命令行創(chuàng)建、讀取、更新和關(guān)閉GitHub上的issue,當(dāng)必要的時(shí)候自動(dòng)打開(kāi)用戶默認(rèn)的編輯器用于輸入文本信息。

練習(xí) 4.12: 流行的web漫畫(huà)服務(wù)xkcd也提供了JSON接口。例如,一個(gè) https://xkcd.com/571/info.0.json 請(qǐng)求將返回一個(gè)很多人喜愛(ài)的571編號(hào)的詳細(xì)描述。下載每個(gè)鏈接(只下載一次)然后創(chuàng)建一個(gè)離線索引。編寫(xiě)一個(gè)xkcd工具,使用這些離線索引,打印和命令行輸入的檢索詞相匹配的漫畫(huà)的URL。

練習(xí) 4.13: 使用開(kāi)放電影數(shù)據(jù)庫(kù)的JSON服務(wù)接口,允許你檢索和下載 https://omdbapi.com/ 上電影的名字和對(duì)應(yīng)的海報(bào)圖像。編寫(xiě)一個(gè)poster工具,通過(guò)命令行輸入的電影名字,下載對(duì)應(yīng)的海報(bào)。



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)