JSON
A single, platform-agnostic JSON codec for Go that optimizes WebAssembly binary size by using zero reflection. It relies on fmt.Fielder for struct encoding/decoding, which is typically generated by ormc.
Index
Architecture
- Zero Reflection: Uses type switches and
fmt.Fielder instead of the reflect package.
- Platform-Agnostic: Identical behavior on all platforms (WASM, Linux, macOS, etc.).
- TinyGo Compatible: Optimized for minimal binary size and memory usage.
- Fielder-Only: Only types implementing
fmt.Fielder can be directly encoded or decoded.
Usage
Structs
Structs MUST implement fmt.Fielder to be supported. This is normally handled by generating code with ormc.
package main
import (
"github.com/tinywasm/fmt"
"github.com/tinywasm/json"
)
// User implements fmt.Fielder (typically via ormc)
type User struct {
Name string
}
func (u *User) Schema() []fmt.Field {
return []fmt.Field{{Name: "name", Type: fmt.FieldText}}
}
func (u *User) Pointers() []any { return []any{&u.Name} }
func main() {
u := User{Name: "Alice"}
var out string
if err := json.Encode(&u, &out); err != nil {
panic(err)
}
// out: {"name":"Alice"}
var result User
if err := json.Decode(out, &result); err != nil {
panic(err)
}
// Recommended: Explicit validation (if result implements fmt.Validator or uses fmt.ValidateFields)
// if err := fmt.ValidateFields('c', &result); err != nil {
// panic(err)
// }
}
API
Encode(data fmt.Fielder, output any) error
Serializes a Fielder to JSON. JSON keys are always taken from field.Name. If field.OmitEmpty is true, the field is skipped if its value is zero.
- data: Must implement
fmt.Fielder.
- output:
*[]byte, *string, or io.Writer.
Parses JSON into a Fielder.
- input:
[]byte, string, or io.Reader.
- data: Must implement
fmt.Fielder.
Supported Types and Limitations
To maintain a minimal footprint and zero reflection, tinywasm/json has specific support and constraints:
Supported Field Types
The encoder and decoder directly support the following fmt.FieldType mappings:
| Go Type |
fmt.FieldType |
JSON Equivalent |
string |
FieldText |
string |
int, int64, etc. |
FieldInt |
number |
float64, float32 |
FieldFloat |
number |
bool |
FieldBool |
boolean |
[]byte |
FieldBlob |
string (escaped) |
[]int |
FieldIntSlice |
array of numbers |
Fielder |
FieldStruct |
object (nested) |
Limitations
- No Reflection: Generic types like
map[string]any, []any, or arbitrary structs NOT implementing fmt.Fielder are NOT supported.
- Root Object Only: Both
Encode and Decode expect a fmt.Fielder as the root element. You cannot directly encode/decode a bare string or number as a standalone JSON value.
- No Maps: Key-value pairs are only supported via struct fields described in the
Schema().
- Simplified Arrays: Currently, only
[]int is supported as a slice type. Other slice types like []string or []float64 are not yet supported.
- No Custom Marshaling: Standard interfaces like
json.Marshaler or json.Unmarshaler are ignored.
- Fielder Contract: Structs must return pointers to all fields in the same order as the schema via
Pointers().
Benchmarks
tinywasm/json is 77% smaller than encoding/json in WASM (~27 KB vs ~119 KB)
and zero-reflect, eliminating reflection overhead and heavy dependencies.
| Benchmark |
tinywasm/json |
encoding/json |
| Encode |
285 ns/op |
276 ns/op |
| Decode |
320 ns/op |
1078 ns/op |
See full results and analysis in benchmarks/README.md.
Contributing
License
See LICENSE for details.