Bu örnekte bir JSON dosyasını okuyup kodunu çözeceğiz. Ancak, dosyayı akış biçiminde okuyacağız. Bunun nedeni, dosyanın ne kadar büyük olacağını bilmiyoruz. Bir kilobyte kadar küçük veya yüzlerce megabyte veya gigabyte kadar büyük olabilir. Dosyanın tamamını hafızaya okumak zorunda kalsaydık, uygulamada birçok şey ters gidebilirdi. Bu yüzden burada JSON akışını kullanıyoruz.


JSON dosyası


[
{
"id": 1,
"name": "user-1"
},
{
"id": 2,
"name": "user-2"
},
{
"id": 3,
"name": "user-3"
},
{
"id": 4,
"name": "user-4"
},
{
"id": 5,
"name": "user-5"
},
...,
{
"id": 1000,
"name": "user-1000"
}
]

User


user.go


package user

type User struct {
ID int `json:"id"`
Name string `json:"name"`
}

stream.go


package user

import (
"encoding/json"
"fmt"
"os"
)

// Entry represents each stream. If the stream fails, an error will be present.
type Entry struct {
Error error
User User
}

// Stream helps transmit each streams withing a channel.
type Stream struct {
stream chan Entry
}

// NewJSONStream returns a new `Stream` type.
func NewJSONStream() Stream {
return Stream{
stream: make(chan Entry),
}
}

// Watch watches JSON streams. Each stream entry will either have an error or a
// User object. Client code does not need to explicitly exit after catching an
// error as the `Start` method will close the channel automatically.
func (s Stream) Watch() <-chan Entry {
return s.stream
}

// Start starts streaming JSON file line by line. If an error occurs, the channel
// will be closed.
func (s Stream) Start(path string) {
// Stop streaming channel as soon as nothing left to read in the file.
defer close(s.stream)

// Open file to read.
file, err := os.Open(path)
if err != nil {
s.stream <- Entry{Error: fmt.Errorf("open file: %w", err)}
return
}
defer file.Close()

decoder := json.NewDecoder(file)

// Read opening delimiter. `[` or `{`
if _, err := decoder.Token(); err != nil {
s.stream <- Entry{Error: fmt.Errorf("decode opening delimiter: %w", err)}
return
}

// Read file content as long as there is something.
i := 1
for decoder.More() {
var user User
if err := decoder.Decode(&user); err != nil {
s.stream <- Entry{Error: fmt.Errorf("decode line %d: %w", i, err)}
return
}
s.stream <- Entry{User: user}

i++
}

// Read closing delimiter. `]` or `}`
if _, err := decoder.Token(); err != nil {
s.stream <- Entry{Error: fmt.Errorf("decode closing delimiter: %w", err)}
return
}
}

main.go


package main

import (
"log"

"github.com/you/internal/user"
)

func main() {
stream := user.NewJSONStream()
go func() {
for data := range stream.Watch() {
if data.Error != nil {
log.Println(data.Error)
}
log.Println(data.User.ID, ":", data.User.Name)
}
}()
stream.Start("users.json")
}

Test


Aşağıda görebileceğiniz gibi, her JSON bloğu tek tek ekranda basılmıştır.


$ go run -race .
2021/04/15 22:45:23 1 : user-1
2021/04/15 22:45:23 2 : user-2
2021/04/15 22:45:23 3 : user-3
2021/04/15 22:45:23 4 : user-4
2021/04/15 22:45:23 5 : user-5
...
2021/04/15 22:45:24 1000 : user-1000