go-json-ice
A simple code generator of JSON marshaler for go and tinygo.
Generated marshaler has fewer runtime dependencies (i.e. it doesn't depend on encoding/json
and reflection) and it's fast.
Motivation
for "restricted" tinygo environment
tinygo doesn't support encoding/json
package because reflection support is not enough: https://tinygo.org/lang-support/stdlib/#encoding-json
This library aims to marshal a struct to JSON on tinygo environment. And it also supposes to run this library in restricted environment; for example, wasm sandbox runtime.
So this library has the following features:
- don't depend on
encoding/json
package
- don't depend on
js
package
- i.e. don't depend on browser wasm runtime
This library has only one dependency for now, strconv
.
encoding/json
uses runtime reflection to marshal and unmarshal, but this library doesn't depend on it. Basically, code generation a marshaller statically by without reflection makes the performance be better.
Synopsis
go generate
with the following struct that uses json-ice
,
//go:generate json-ice --type=AwesomeStruct
type AwesomeStruct struct {
Foo string `json:"foo"`
Bar string `json:"bar,omitempty"`
}
then it generates marshaler code as MarshalAwesomeStructAsJSON(s *AwesomeStruct) ([]byte, error)
; you can use that like the following:
marshaled, err := MarshalAwesomeStructAsJSON(&AwesomeStruct{
Foo: "buz",
Bar: "",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", marshaled) // => {"foo":"buz"}
Usage of the generator command
json-ice:
-cap-size int
[optional] a cap-size of a byte slice buffer for marshaling; by default, it calculates this value automatically
-output string
[optional] output file name (default "srcdir/<type>_gen.go")
-toplevel-array
[optional] generate a marshaler for toplevel-array with given type by "--type" (e.g. "[]string")
-toplevel-map
[optional] generate a marshaler for toplevel-array with given type by "--type" (e.g. "[]string")
-type string
[mandatory] a type name
-version
show version information
How to install
$ go get -u github.com/moznion/go-json-ice/cmd/json-ice
also you can get the pre-built binaries on Releases.
How it works
Parses given struct and generates a marshaler according to the parsed result; it means resolve fields statically without runtime reflection.
It calculates a cap-size of a byte slice buffer for marshaling automatically, but you can specify it by yourself.
If you need more throughput, it is a good idea to specify it as a bigger cap-size, on the other hand, if you would like to save the amount of memory, you can put it as a smaller (e.g. 1
) cap-size.
Benchmark
=== auto capsize ===
goos: darwin
goarch: amd64
pkg: github.com/moznion/go-json-ice/benchmark
BenchmarkMarshal_EncodingJSON-12 1179334 1010 ns/op 320 B/op 1 allocs/op
BenchmarkMarshal_JSONIce-12 2014201 629 ns/op 384 B/op 1 allocs/op
PASS
ok github.com/moznion/go-json-ice/benchmark 4.099s
=== minimum capsize => 1 ===
goos: darwin
goarch: amd64
pkg: github.com/moznion/go-json-ice/benchmark
BenchmarkMarshal_EncodingJSON-12 1136454 1003 ns/op 320 B/op 1 allocs/op
BenchmarkMarshal_JSONIce-12 1421254 846 ns/op 1009 B/op 7 allocs/op
PASS
ok github.com/moznion/go-json-ice/benchmark 4.287s
auto capsize
benchmarks it the default situation, and minimum capsize
benchmarks it with the minimum cap-size (i.e. in worst performance case: 1
).
make bench
command benchmarks the performance.
FAQ
Does this library support JSON unmarshaling?
No, this only supports marshaling. If you are looking for a way to marshal JSON with tinygo, please consider using buger/jsonparser.
How to marshal non-primitive types
For example,
type Foo struct {
Hoge string `json:"hoge"`
}
//go:generate json-ice --type=Bar
type Bar struct {
Foo *Foo `json:"foo"`
}
In this case, a marshaler for Bar
tries to marshal Foo
by Foo.MarshalJSON() ([]byte, error)
. It means it expects the target type implements json.Marshaler
when it tries to marshal a non-primitive type.
The easiest way to marshal the struct like the above is the follwoing:
//go:generate json-ice --type=Foo
type Foo struct {
Hoge string `json:"hoge"`
}
//go:generate json-ice --type=Bar
type Bar struct {
Foo *Foo `json:"foo"`
}
func (f *Foo) MarshalJSON() ([]byte, error) {
return MarshalFooAsJSON(f)
}
How to marshal toplevel array and map JSON
It is necessary to generate marshaler code by like the following instructions:
//go:generate json-ice --type=[]string --toplevel-array
- then, it generates a marshaler for toplevel array
[]string
as MarshalStringArrayAsJSON(st []string) ([]byte, error)
//go:generate json-ice --type=map[string]string --toplevel-map
- then, it generates a marshaler for toplevel map
map[string]string
as MarshalStringToStringMapAsJSON(mt map[string]string) ([]byte, error)
Restrictions / Known issues
- it cannot marshal
interface{}
values
- because it doesn't use reflection so it cannot resolve the actual type dynamically
- in other words, the target type has to be resolvable as statically
- it cannot marshal
named type
and type alias
types automatically
- if you would like to marshal such them, please implement
json.Marshaler
on the type as like as the above FAQ answer.
How to build binaries
Binaries are built and uploaded by goreleaser. Please refer to the configuration file: .goreleaser.yml
Author
moznion (moznion@gmail.com)