json

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Apr 18, 2026 License: MIT Imports: 3 Imported by: 2

README

tinywasm/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.

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

Code Generation with ormc

fmt.Fielder is never written by hand. The ormc CLI (from github.com/tinywasm/orm) generates Schema() and Pointers() for every struct in the package.

Two modes, controlled by a doc comment on the struct:

Struct type Doc comment What ormc generates
DB-backed model (none) ModelName(), Schema(), Pointers(), Validate(), ReadOne*, ReadAll*
Transport / response // ormc:formonly Schema(), Pointers() only
go install github.com/tinywasm/orm/cmd/ormc@latest
ormc   # run at the module root; writes/rewrites *_orm.go
Structs

Structs MUST implement fmt.Fielder to be supported. This is 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 to JSON. If data also implements fmt.FielderSlice, it is encoded as a JSON array [...]; otherwise as an object {...}. JSON keys are taken from field.Name. If field.OmitEmpty is true, the field is skipped when its value is zero.

  • data: fmt.Fielder{...} · fmt.FielderSlice[...]
  • output: *[]byte, *string, or io.Writer.
Decode(input any, data fmt.Fielder) error

Parses JSON into data. If data also implements fmt.FielderSlice, input must be a JSON array [...]; otherwise input must be an object {...}.

  • input: []byte, string, or io.Reader.
  • data: fmt.Fielder → expects {...} · fmt.FielderSlice → expects [...]

Pre-Serialized JSON with fmt.RawJSON

For fields containing pre-serialized JSON (e.g., API responses that nest raw JSON), use fmt.RawJSON:

// ormc:formonly
type APIResponse struct {
    Status string     // regular string
    Data   fmt.RawJSON // pre-serialized JSON — emitted inline, not quoted
}

func main() {
    resp := APIResponse{
        Status: "ok",
        Data:   `{"id":123,"name":"Alice"}`, // already JSON-formatted string
    }

    var out string
    json.Encode(&resp, &out)
    // out: {"status":"ok","data":{"id":123,"name":"Alice"}}
    //                        ↑ no quotes around data — emitted inline
}

Why fmt.RawJSON?

  • Eliminates linter warnings about non-standard json tag options
  • Self-documenting: the type signals that the field contains pre-serialized JSON
  • Zero overhead: type alias with =, no runtime boxing

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)
fmt.RawJSON FieldRaw string (pre-serialized JSON, emitted inline)
[]int FieldIntSlice array of numbers
Fielder FieldStruct object (nested)
[]Fielder FieldStructSlice array of objects
Limitations
  • No Reflection: Generic types like map[string]any, []any, or arbitrary structs NOT implementing fmt.Fielder are NOT supported.
  • Root Object or Array: Encode/Decode accept fmt.Fielder (→ {}) or fmt.FielderSlice (→ []) at the root. Bare strings, numbers, or null as root values are not supported.
  • No Maps: Key-value pairs are only supported via struct fields described in the Schema().
  • Simplified Arrays: Supported slice types include []int and collections of objects (via FieldStructSlice). Other 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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decode added in v0.1.0

func Decode(input any, data fmt.Fielder) error

Decode parses JSON into a Fielder. input: []byte | string | io.Reader.

func Encode added in v0.1.0

func Encode(data fmt.Fielder, output any) error

Encode serializes a Fielder to JSON. output: *[]byte | *string | io.Writer.

Types

This section is empty.

Directories

Path Synopsis
benchmarks
clients/stdlib command
web command

Jump to

Keyboard shortcuts

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