zoukankan      html  css  js  c++  java
  • Go 与 JSON

    Go 中通过标准库encoding/jsonencoding.xmlencoding/asn1和其他库对 JSON、XML、ASN.1 和其他类型的标准的编码和解码提供了良好的支持,这里对使用最多的encoding/json进行一个简要地描述。

    看下面一个结构体类型(Year 和 Color 后面的字符串是「成员标签」):

    type Movie struct {
        Title string
        Yeat int `json:"released"`
        Color bool `json:"Color,omitempty"`
        Actors []string
    }
    
    var movies = []Movie {
        {
            Title: "Casablanca",
            Year: 1942,
            Color: false,
            Actors: []string{"A", "B"},
        },
        {
            Title: "Cool Hand Luke",
            Year: 1967,
            Color: true,
            Actors: []string{"C", "D"},
        },
    }
    

    显然结构体很适合通过 JSON 来描述内部的数据,无论是从 Go 对象转换成 JSON 还是从 JSON 转换成 Go 对象都是容易的。

    Go 的数据结构转换为 JSON:marshal

    将 Go 的数据结构转换成 JSON 称为「marshal」,marshal 是通过json.Marshal来实现的:

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

    Marshal 会生成一个字节 slice,其中包含一个不带有任何多余空白字符的很长的字符串。上面的结果会是这样的:

    [{"Title":"Casablanca","released":1942,"Actors":["A","B"]},{"Title":"CoolHandLuke","released":1967,"color":true,"Actors":["C","D"]}]
    

    显然上面将所有的信息都放在一行之中表示,难以阅读。为了方便阅读,MarshalIndent可以输出整齐格式化后的结果。这个函数有两个参数,一个是定义每行输出的前缀字符串,另一个是定义锁进的字符串。

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

    这样输出的就是清晰、易于阅读的字符串了:

    [
        {
            "Title": "Casablanca",
            "released": 1942,
            "Actors": [
                "A",
                "B"
            ]
        },
        {
            "Title": "Cool Hand Luke",
            "released": 1967,
            "color": true,
            "Actors": [
                "C",
                "D"
            ]
        }
    ]
    

    可以看到 Marshal 能够使用 Go 结构体成员的名称作为 JSON 对象中字段的名称(通过反射实现),因此,只有可以导出的成员能够转换成 JSON 对象,这也是为什么一般都将 Go 结构体中的所有成员定义为首字母大写。

    仔细观察上面的 JSON 对象,我们可以发现,结构体成员的Year对应地转化为了releasedColor转换为了color,这就是通过前面的成员标签field tag定义实现的。

    「成员标签」定义结构体成员在编译期间关联的一些元信息:

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

    成员标签的定义可以是任何字符串,但按照习惯,是由一串由空格分开的标签键值对key:"value"组成;因为标签的值使用双引号括起来,因此一般标签都是原生的字符串字面量。

    上例中,键json控制了包encoding/json的行为,(其他encoding/...包也遵循这个规则),标签的第一部分制定了 Go 结构体成员对应 JSON 中字段的名字。成员的标签一般便是如此使用。

    而 Color 标签中还有一个额外的选项:omitempty;这个选项表示:如果这个成员的值是零值或者为空,则不输出这个成员到 JSON 中。所以对于电影「Casablanse」,并没有输出成员 Color 到 JSON 中。

    JSON 转换为 Go 的数据结构:unmarshal

    marshal 的逆操作便是将 JSON 转换为 Go 数据结构,这个过程称为「unmarshal」,由json.Unmarshal实现,下面代码将电影的 JSON 数据转换为结构体 slice。

    这里我们指定结构体中仅含 Title 这个成员,通过合理地定义 Go 的数据结构,我们可以选择解码 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}]
    

    下面我们通过看一个例子,这个例子中我们查询一个 github 页面的 issues 接口:

    1.新建一个github包,在其中定义需要的类型和常量

    这里我们固定查询的 github 仓库,并定义查询结果的结构体IssuesSearchResult

    // 包 github 提供 GitHub issue 跟踪接口的 Go API
    
    /* 这里定义了查询 issuse 所用的类型和常量 */
    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 // markdown 格式
    }
    
    type User struct {
        Login string
        HTMLURL string `json:"html_url"`
    }
    

    2.编写函数SearchIssues发送 HTTP 请求,并将相应解析为 JSON

    由于用户查询请求可能存在一些特殊字符,例如&?这些属于 URL 的特殊字符,因此我们还要使用url.QueryEscape来保证它们拥有正确含义:

    package github
    
    import (
        "encoding/json"
        "fmt"
        "net/http"
        "net/url"
        "strings"
    )
    
    func  SearchIssues(terms []string) (*IssuesSearchResult, error) {
        // 发起 HTTP 请求
        q := url.QueryEscape(strings.Join(terms, " "))
        resp, err := http.Get(IssuesURL + "?q=" + q)
        if err != nil {
            return nil, err
        }
        // 在所有的分支上面关闭 resp.Body
        defer resp.Body.Close()
        if resp.StatusCode != http.StatusOK {
            return nil, fmt.Errorf("search query failed: %s", resp.Status)
        }
        // 将 response 解析为 JSON
        var result IssuesSearchResult
        if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
            return nil, err
        }
        return &result, nil
    }
    

    不同于之前使用的json.Unmarshal,这里我们使用来流式解码器json.Decoder来对 JSON 进行解码,它能够从字节流中解码出多个 JSON 实例,相对应的,也存在用于编码的json.Encoder

    3.调用之前编写的 github 包,输出查询的 issues

    // 将复合搜索条件的 issues 输出为一个表格
    package main
    
    import (
        "fmt"
        "log"
        "os"
        // 导入刚刚编写的包,包路径依照环境变量设置而不同
        "github.com/Bylight/ch4/github"
    )
    
    func main() {
        // 获取结果
        result, err := github.SearchIssues(os.Args[1:])
        if err != nil {
            log.Fatal(err)
        }
        // 将结果输出
        fmt.Printf("%d issues:
    ", result.TotalCount)
        for _, item := range result.Items {
            fmt.Printf("%#-5d %9.9s %.55s
    ", item.Number, item.User.Login, item.Title)
        }
    }
    

    最后我们运行最后编写的 main 函数,我们指定它搜索 Go 项目中 issue 跟踪接口,查找关于 JSON 编码的 Open 状态的 bug 列表:

    ➜  go run issues.go repo:golang/go is:open json decoder
    43 issues:
    33416   bserdar encoding/json: This CL adds Decoder.InternKeys
    34647 babolivie encoding/json: fix byte counter increments when using d
    5901        rsc encoding/json: allow override type marshaling
    29035    jaswdr proposal: encoding/json: add error var to compare  the
    34543  maxatome encoding/json: Unmarshal & json.(*Decoder).Token report
    32779       rsc proposal: encoding/json: memoize strings during decode?
    28923     mvdan encoding/json: speed up the decoding scanner
    11046     kurin encoding/json: Decoder internally buffers full input
    ...
    
  • 相关阅读:
    sql server 2005的分页函数ROW_NUMBER
    Silverlight 皮肤(主题)动态切换
    Silverlight 地图导航
    WPF模板概述(数据模板)
    Silverlight MVVM Prism WCF RIA项目框架
    多线程通中的AutoResetEvent与ManualResetEvent
    Silverlight 图形报表 线形图 柱形图 饼图
    WPF 重要新概念读书笔记(转)
    Net基本概念和原理
    数据库辅助工具SqlDbx
  • 原文地址:https://www.cnblogs.com/Bylight/p/12061100.html
Copyright © 2011-2022 走看看