ZhangZhihui's Blog  

Problem: You want to parse JSON data from a stream.


Solution: Create structs to contain the JSON data. Create a decoder using NewDecoder in the encoding/json package, then call Decode on the decoder to decode data into the structs.

 

In this first JSON file you have an array of three JSON objects (part of the data is truncated to make it easier to read):

复制代码
[
    {
        "name": "Luke  Skywalker",
        "height": "172",
        "mass": "77",
        "hair_color": "blond",
        "skin_color": "fair",
        "eye_color": "blue",
        "birth_year": "19BBY",
        "gender": "male"
    },
    {
        "name": "C - 3PO",
        "height": "167",
        "mass": "75",
        "hair_color": "n/a",
        "skin_color": "gold",
        "eye_color": "yellow",
        "birth_year": "112BBY",
        "gender": "n/a"
    },
    {
        "name": "R2 - D2",
        "height": "96",
        "mass": "32",
        "hair_color": "n/a",
        "skin_color": "white,  blue",
        "eye_color": "red",
        "birth_year": "33BBY",
        "gender": "n/a"
    }
]
复制代码

To read this, you can use Unmarshal by unmarshalling into an array of Person structs:

复制代码
type Person struct {
    Name string `json:"name"`
    Height string `json:"height"`
    Mass string `json:"mass"`
    HairColor string `json:"hair_color"`
    SkinColor string `json:"skin_color"`
    EyeColor string `json:"eye_color"`
    BirthYear string `json:"birth_year"`
    Gender string `json:"gender"`
    Homeworld string `json:"homeworld"`
    Films []string `json:"films"`
    Species []string `json:"species"`
    Vehicles []string `json:"vehicles"`
    Starships []string `json:"starships"`
    Created time.Time `json:"created"`
    Edited time.Time `json:"edited"`
    URL string `json:"url"`
}

func chkPrtErr(err error, msg string) {
    if err != nil {
        log.Printf("%s: %s\n", msg, err)
    }
}

func unmarshalStructArray() (people []Person) {
    file, err := os.Open("people.json")
    chkPrtErr(err, "Error opening json file")
    defer file.Close()

    data, err := io.ReadAll(file)
    chkPrtErr(err, "Error reading json data")

    err = json.Unmarshal(data, &people)
    chkPrtErr(err, "Error unmarshalling json data")

    return
}
复制代码

This will result in an output like this:

复制代码
[]json.Person{
    {
        Name:       "Luke  Skywalker",
        Height:     "172",
        Mass:       "77",
        HairColor:  "blond",
        SkinColor:  "fair",
        EyeColor:   "blue",
        BirthYear:  "19BBY",
        Gender:     "male",
        Homeworld:  "",
        Films:      nil,
        Species:    nil,
        Vehicles:   nil,
        Starships:  nil,
        Created:    time.Date(1,  time.January,  1,  0,  0,  0,  0,  time.UTC),
        Edited:     time.Date(1,  time.January,  1,  0,  0,  0,  0,  time.UTC),
        URL:        "",
    },
    {
        Name:       "C - 3PO",
        Height:     "167",
        Mass:       "75",
        HairColor:  "n/a",
        SkinColor:  "gold",
        EyeColor:   "yellow",
        BirthYear:  "112BBY",
        Gender:     "n/a",
        Homeworld:  "",
        Films:      nil,
        Species:    nil,
        Vehicles:   nil,
        Starships:  nil,
        Created:    time.Date(1,  time.January,  1,  0,  0,  0,  0,  time.UTC),
        Edited:     time.Date(1,  time.January,  1,  0,  0,  0,  0,  time.UTC),
        URL:        "",
    },
    {
        Name:       "R2 - D2",
        Height:     "96",
        Mass:       "32",
        HairColor:  "n/a",
        SkinColor:  "white,  blue",
        EyeColor:   "red",
        BirthYear:  "33BBY",
        Gender:     "n/a",
        Homeworld:  "",
        Films:      nil,
        Species:    nil,
        Vehicles:   nil,
        Starships:  nil,
        Created:    time.Date(1,  time.January,  1,  0,  0,  0,  0,  time.UTC),
        Edited:     time.Date(1,  time.January,  1,  0,  0,  0,  0,  time.UTC),
        URL:        "",
    },
}
复制代码

This is an array of Person structs, which you get after unmarshalling a single JSON array.

 

However, when you get a stream of JSON objects, this is no longer possible. Here is another JSON file, one that is representative of a JSON data stream:

复制代码
{
    "name": "Luke  Skywalker",
    "height": "172",
    "mass": "77",
    "hair_color": "blond",
    "skin_color": "fair",
    "eye_color": "blue",
    "birth_year": "19BBY",
    "gender": "male"
} 
{
    "name": "C - 3PO",
    "height": "167",
    "mass": "75",
    "hair_color": "n/a",
    "skin_color": "gold",
    "eye_color": "yellow",
    "birth_year": "112BBY",
    "gender": "n/a"
} 
{
    "name": "R2 - D2",
    "height": "96",
    "mass": "32",
    "hair_color": "n/a",
    "skin_color": "white,  blue",
    "eye_color": "red",
    "birth_year": "33BBY",
    "gender": "n/a"
}
复制代码

Notice that this is not a single JSON object but three consecutive JSON objects. This is no longer a valid JSON file, but it’s something you can get when you read the Body of a http.Response struct. If you try to read this using Unmarshal you will get an error:

Error  unmarshalling  json  data:  invalid  character  '{'  after  top - level  value

However, you can parse it by decoding it using a Decoder:

复制代码
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "os"
    "time"

    "github.com/kr/pretty"
)

type Person struct {
    Name      string    `json:"name"`
    Height    string    `json:"height"`
    Mass      string    `json:"mass"`
    HairColor string    `json:"hair_color"`
    SkinColor string    `json:"skin_color"`
    EyeColor  string    `json:"eye_color"`
    BirthYear string    `json:"birth_year"`
    Gender    string    `json:"gender"`
    Homeworld string    `json:"homeworld"`
    Films     []string  `json:"films"`
    Species   []string  `json:"species"`
    Vehicles  []string  `json:"vehicles"`
    Starships []string  `json:"starships"`
    Created   time.Time `json:"created"`
    Edited    time.Time `json:"edited"`
    URL       string    `json:"url"`
}

func decode(p chan Person) {
    file, err := os.Open("people_stream.json")
    if err != nil {
        log.Println("Error  opening  json  file:", err)
    }
    defer file.Close()

    decoder := json.NewDecoder(file)
    for {
        var person Person
        err = decoder.Decode(&person)
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Println("Error  decoding  json  data:", err)
            break
        }
        p <- person
    }
    close(p)
}
复制代码

First, you create a decoder using json.NewDecoder and passing it the reader, in this case, it’s the file you read from. Then while you’re looping in the for loop, you call Decode on the decoder, passing it the struct you want to store the data in. If all goes well, every time it loops, a new Person struct instance is created from the data. You can use the data then. If there is no more data in the reader, i.e., you hit io.EOF , you’ll break from the for loop.

In the case of the preceding code, you pass in a channel, in which you store the Person struct instance in every loop. When you’re done reading all the JSON in the file, you’ll close the channel:

复制代码
func main() {
    p := make(chan Person)
    go decode(p)
    for {
        person, ok := <-p
        if ok {
            fmt.Printf("%# v\n", pretty.Formatter(person))
        } else {
            break
        }
    }
}
复制代码

Here’s the output from the code:

复制代码
zzh@ZZHPC:/zdata/MyPrograms/Go/study$ go run main.go
main.Person{
    Name:      "Luke  Skywalker",
    Height:    "172",
    Mass:      "77",
    HairColor: "blond",
    SkinColor: "fair",
    EyeColor:  "blue",
    BirthYear: "19BBY",
    Gender:    "male",
    Homeworld: "",
    Films:     nil,
    Species:   nil,
    Vehicles:  nil,
    Starships: nil,
    Created:   time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    Edited:    time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    URL:       "",
}
main.Person{
    Name:      "C - 3PO",
    Height:    "167",
    Mass:      "75",
    HairColor: "n/a",
    SkinColor: "gold",
    EyeColor:  "yellow",
    BirthYear: "112BBY",
    Gender:    "n/a",
    Homeworld: "",
    Films:     nil,
    Species:   nil,
    Vehicles:  nil,
    Starships: nil,
    Created:   time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    Edited:    time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    URL:       "",
}
main.Person{
    Name:      "R2 - D2",
    Height:    "96",
    Mass:      "32",
    HairColor: "n/a",
    SkinColor: "white,  blue",
    EyeColor:  "red",
    BirthYear: "33BBY",
    Gender:    "n/a",
    Homeworld: "",
    Films:     nil,
    Species:   nil,
    Vehicles:  nil,
    Starships: nil,
    Created:   time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    Edited:    time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
    URL:       "",
}
复制代码

You can see that three Person structs are being printed here, one after another, as opposed to the earlier one that was an array of Person structs.

 

A question that sometimes arises is when should you use Unmarshal and when should you use Decode ?
Unmarshal is easier to use for a single JSON object, but it won’t work when you have a stream of them coming in from a reader. Also, its simplicity means it’s not as flexible; you just get the whole JSON data at a go.
Decode , on the other hand, works well for both single JSON objects and streaming JSON data. Also, with Decode you can do stuff with the JSON at a finer level without needing to get the entire JSON data out first. This is because you can inspect the JSON as it comes in, even at a token level. The only slight drawback is that it is more verbose.
In addition, Decode is a bit faster.

 

posted on   ZhangZhihuiAAA  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
 
点击右上角即可分享
微信分享提示