go-json-ice

module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2021 License: BSD-3-Clause

README

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.

Better performance

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.

Performance

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)

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL