Skip to content

Commit 1f0094c

Browse files
authored
init commit (#1)
1 parent 26259da commit 1f0094c

14 files changed

+829
-0
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
defaults:
9+
run:
10+
shell: bash
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v3
18+
19+
- name: Setup Go
20+
uses: actions/setup-go@v4
21+
with:
22+
go-version: 'stable'
23+
24+
- name: Go Format
25+
run: |
26+
if [ -n "$(gofmt -l .)" ]; then
27+
echo "Go code is not formatted:"
28+
gofmt -d .
29+
exit 1
30+
fi
31+
32+
- name: Go Test
33+
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic
34+
35+
- name: Codecov
36+
run: bash <(curl -s https://codecov.io/bash)

README.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,129 @@
11
# go-jsonstream
22
JSON Stream (JSON Lines, JSON Array) Go Library
3+
4+
### Installation
5+
6+
```bash
7+
go get github.com/Wing924/go-jsonstream
8+
```
9+
10+
## How to use
11+
12+
### Encode
13+
14+
```go
15+
// encode as JSON Lines stream
16+
out := bytes.NewBuffer(nil)
17+
18+
sender := NewJSONLinesEncoder(out)
19+
sender.Send("foo")
20+
sender.Send(map[string]string{"bar": "baz"})
21+
sender.Send(123)
22+
sender.Done()
23+
24+
fmt.Println(out.String())
25+
// Will output:
26+
// "foo"
27+
// {"bar":"baz"}
28+
// 123
29+
```
30+
31+
```go
32+
// encode as JSON Array stream
33+
out := bytes.NewBuffer(nil)
34+
35+
sender := NewJSONArrayEncoder(out)
36+
sender.Send("foo")
37+
sender.Send(map[string]string{"bar": "baz"})
38+
sender.Send(123)
39+
sender.Done()
40+
41+
fmt.Println(out.String())
42+
// Will output:
43+
// [
44+
// "foo"
45+
// ,{"bar":"baz"}
46+
// ,123
47+
// ]
48+
```
49+
50+
### Decode
51+
52+
```go
53+
// encode as JSON Lines stream
54+
in := bytes.NewBufferString(`
55+
{"name":"Barbie"}
56+
{"name":"Barbie"}
57+
{"name":"Ken"}
58+
`)
59+
60+
var data struct{
61+
Name string `json:"name"`
62+
}
63+
64+
receiver := jsonstream.NewJSONLinesDecoder(in)
65+
for {
66+
err := receiver.Receive(&data)
67+
if err != nil {
68+
if err == io.EOF {
69+
break
70+
}
71+
panic(err)
72+
}
73+
fmt.Printf("%+v\n", data)
74+
}
75+
76+
// will output
77+
// {Name:Barbie}
78+
// {Name:Barbie}
79+
// {Name:Ken}
80+
```
81+
82+
```go
83+
// encode as JSON Array stream
84+
in := bytes.NewBufferString(`[{"name":"Barbie"},{"name":"Barbie"},{"name":"Ken"}]`)
85+
86+
var data struct{
87+
Name string `json:"name"`
88+
}
89+
90+
receiver := jsonstream.NewJSONArrayDecoder(in)
91+
for {
92+
err := receiver.Receive(&data)
93+
if err != nil {
94+
if err == io.EOF {
95+
break
96+
}
97+
panic(err)
98+
}
99+
fmt.Printf("%+v\n", data)
100+
}
101+
102+
// will output
103+
// {Name:Barbie}
104+
// {Name:Barbie}
105+
// {Name:Ken}
106+
```
107+
108+
### Use [go-json](https://github.com/goccy/go-json) instead of `encoding/json`
109+
110+
The `go-json` is fast JSON encoder/decoder compatible with `encoding/json` for Go.
111+
112+
```go
113+
// Change the global
114+
jsonstream.DefaultConfig = jsonstream.GoJSONConfig
115+
116+
// now use go-json to encode/decode JSON
117+
jsonstream.NewJSONArrayEncoder(out)
118+
jsonstream.NewJSONArrayDecoder(in)
119+
jsonstream.NewJSONLinesEncoder(out)
120+
jsonstream.NewJSONLinesDecoder(in)
121+
```
122+
123+
```go
124+
// Change individual
125+
jsonstream.NewJSONArrayEncoderWithConfig(out, jsonstream.GoJSONConfig)
126+
jsonstream.NewJSONArrayDecoderWithConfig(in, jsonstream.GoJSONConfig)
127+
jsonstream.NewJSONLinesEncoderWithConfig(out, jsonstream.GoJSONConfig)
128+
jsonstream.NewJSONLinesDecoderWithConfig(in, jsonstream.GoJSONConfig)
129+
```

bench_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package jsonstream
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"testing"
8+
)
9+
10+
type Format int
11+
12+
const (
13+
FormatJSONLines Format = iota
14+
FormatJSONArray
15+
)
16+
17+
var formatNames = map[Format]string{
18+
FormatJSONLines: "JSON Lines",
19+
FormatJSONArray: "JSON Array",
20+
}
21+
22+
func (f Format) String() string {
23+
return formatNames[f]
24+
}
25+
26+
func NewEncoder(out io.Writer, format Format, cfg *Config) Sender {
27+
switch format {
28+
case FormatJSONLines:
29+
return NewJSONLinesEncoderWithConfig(out, cfg)
30+
case FormatJSONArray:
31+
return NewJSONArrayEncoderWithConfig(out, cfg)
32+
default:
33+
panic(fmt.Errorf("unknown format: %v", format))
34+
}
35+
}
36+
37+
func NewDecoder(in io.Reader, format Format, cfg *Config) Receiver {
38+
switch format {
39+
case FormatJSONLines:
40+
return NewJSONLinesDecoderWithConfig(in, cfg)
41+
case FormatJSONArray:
42+
return NewJSONArrayDecoderWithConfig(in, cfg)
43+
default:
44+
panic(fmt.Errorf("unknown format: %v", format))
45+
}
46+
}
47+
48+
func BenchmarkEncoder(b *testing.B) {
49+
for _, c := range configs[1:] {
50+
for _, f := range []Format{FormatJSONLines, FormatJSONArray} {
51+
b.Run(c.name+"_"+f.String(), func(b *testing.B) {
52+
sender := NewEncoder(io.Discard, f, c.config)
53+
for i := 0; i < b.N; i++ {
54+
_ = sender.Send(benchSample)
55+
}
56+
_ = sender.Done()
57+
})
58+
}
59+
}
60+
}
61+
62+
func BenchmarkDecoder(b *testing.B) {
63+
for _, c := range configs[1:] {
64+
for _, f := range []Format{FormatJSONLines, FormatJSONArray} {
65+
b.Run(c.name+"_"+f.String(), func(b *testing.B) {
66+
input := bytes.NewBuffer(nil)
67+
sender := NewEncoder(input, f, c.config)
68+
for i := 0; i < b.N; i++ {
69+
_ = sender.Send(benchSample)
70+
}
71+
_ = sender.Done()
72+
73+
var data Small
74+
receiver := NewDecoder(input, f, c.config)
75+
b.ResetTimer()
76+
for i := 0; i < b.N; i++ {
77+
_ = receiver.Receive(&data)
78+
}
79+
80+
})
81+
}
82+
}
83+
}

config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package jsonstream
2+
3+
import (
4+
"io"
5+
)
6+
7+
type (
8+
Config struct {
9+
NewEncoder func(out io.Writer) JSONEncoder
10+
NewDecoder func(in io.Reader) JSONDecoder
11+
}
12+
13+
JSONEncoder interface {
14+
Encode(v any) error
15+
}
16+
17+
JSONDecoder interface {
18+
More() bool
19+
RuneToken() (token rune, others any, err error)
20+
Decode(v any) error
21+
}
22+
)
23+
24+
var DefaultConfig = StdConfig

config_gojson.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package jsonstream
2+
3+
import (
4+
"github.com/goccy/go-json"
5+
"io"
6+
)
7+
8+
var GoJSONConfig = &Config{
9+
NewEncoder: NewGoJSONEncoder,
10+
NewDecoder: NewGoJSONDecoder,
11+
}
12+
13+
func NewGoJSONEncoder(out io.Writer) JSONEncoder {
14+
return json.NewEncoder(out)
15+
}
16+
17+
type goJSONDecoder struct {
18+
*json.Decoder
19+
}
20+
21+
func NewGoJSONDecoder(in io.Reader) JSONDecoder {
22+
return &goJSONDecoder{json.NewDecoder(in)}
23+
}
24+
25+
func (d *goJSONDecoder) RuneToken() (token rune, others any, err error) {
26+
t, err := d.Decoder.Token()
27+
if err != nil {
28+
return 0, nil, err
29+
}
30+
if d, ok := t.(json.Delim); ok {
31+
return rune(d), nil, nil
32+
}
33+
return 0, t, nil
34+
}

config_std.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package jsonstream
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
)
7+
8+
var StdConfig = &Config{
9+
NewEncoder: NewStdJSONEncoder,
10+
NewDecoder: NewStdJSONDecoder,
11+
}
12+
13+
func NewStdJSONEncoder(out io.Writer) JSONEncoder {
14+
return json.NewEncoder(out)
15+
}
16+
17+
type stdJSONDecoder struct {
18+
*json.Decoder
19+
}
20+
21+
func NewStdJSONDecoder(in io.Reader) JSONDecoder {
22+
return &stdJSONDecoder{json.NewDecoder(in)}
23+
}
24+
25+
func (d *stdJSONDecoder) RuneToken() (token rune, others any, err error) {
26+
t, err := d.Decoder.Token()
27+
if err != nil {
28+
return 0, nil, err
29+
}
30+
if d, ok := t.(json.Delim); ok {
31+
return rune(d), nil, nil
32+
}
33+
return 0, t, nil
34+
}

go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/Wing924/go-jsonstream
2+
3+
go 1.21.0
4+
5+
require (
6+
github.com/goccy/go-json v0.10.2
7+
github.com/stretchr/testify v1.8.4
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
gopkg.in/yaml.v3 v3.0.1 // indirect
14+
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
4+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
8+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
12+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

stream.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package jsonstream
2+
3+
type (
4+
Receiver interface {
5+
Receive(data any) error
6+
}
7+
8+
Sender interface {
9+
Send(data any) error
10+
Done() error
11+
}
12+
)

0 commit comments

Comments
 (0)