Gothic Framework G symbol

Typed JSON: Encode Request Bodies

Encode[T] is the mirror of Decode[T]: it turns a typed Go struct into JSON bytes for a request body — reflection-free and generated at build time. Pair it with BodyBytes on FetchConfig to send structured data to your server.

Same rules as Decode: the type argument is explicit and T must be a struct in a net/http-free package (like src/shared). nil slices, pointers, and maps encode as null.

import (
	. "github.com/gothicframework/core/wasm"
	"github.com/your-org/your-app/src/shared" // the same net/http-free package
)

CreateWasmFunc("save", func() {
	user := shared.User{
		Name:     "Ada",
		Age:      36,
		Username: "ada_l",
		Tags:     []string{"pioneer", "math"},
		Address:  shared.Address{City: "London", Zip: "SW1"},
	}

	// Encode[T] marshals a typed struct to JSON bytes — reflection-free,
	// generated at build time. The type argument is explicit, like Decode.
	body := Encode[shared.User](user)

	resp, err := Fetch("/api/user", FetchConfig{
		Method:    "POST",
		Headers:   map[string]string{"Content-Type": "application/json"},
		BodyBytes: body,
	})
	if err != nil || !resp.OK() {
		SetText("status", "save failed")
		return
	}
	SetText("status", "saved!")
})

Because both sides import the same struct, an EncodePOSTDecode round trip preserves every field, snake_case renames included. That's the own-BFF promise: no drift between client and server.

// Round trip: the SAME shared.User survives Encode -> POST -> Decode,
// proving client and server agree on the wire format with zero drift.
CreateWasmFunc("roundtrip", func() {
	sent := shared.User{Name: "Ada", Age: 36, Username: "ada_l"}

	resp, err := Fetch("/api/echo", FetchConfig{
		Method:    "POST",
		Headers:   map[string]string{"Content-Type": "application/json"},
		BodyBytes: Encode[shared.User](sent),
	})
	if err != nil {
		return
	}

	got, err := Decode[shared.User](resp)
	if err != nil {
		return
	}
	SetText("name", got.Name)                          // "Ada"
	SetText("user", got.Username)                      // "ada_l" (rename survives)
	SetText("match", strconv.FormatBool(got == sent))  // "true"
})

When to reach for typed JSON vs HTML. If you need the data — to branch on a field, sum a total, or build a request body — use Fetch + Decode/Encode. If you just need to drop server-rendered HTML into the page, use HTMX instead — that's next.

Want to render HTML fragments, swap the DOM, and boot nested components — all from Go? Meet the HTMX struct next!