Type mapping
This reference describes canonical, schema-directed application JSON, as used by the schema JSON codecs and MCP structured values. The schema determines the meaning of each value; these shapes are not JavaScript’s JSON.stringify rules for guest objects.
The raw invocation REST API uses a different representation: kind/value
tagged values, positional record fields, and a schema graph. CLI input
literals use native syntax, not these JSON examples. CLI
--format json output is a structured result envelope that includes the typed result, not a bare
canonical value. Custom HTTP APIs can also use raw text or
binary bodies; not every HTTP endpoint uses canonical JSON.
For a record with one u32 field named count:
| Canonical application JSON | REST wire value (schema supplied separately) |
|---|---|
{"count":5} | {"kind":"record","value":{"fields":[{"kind":"u32","value":5}]}} |
Shared JSON rules
- Strings, booleans, and integers up to 32 bits use ordinary JSON strings, booleans, and numbers. A character is a string containing one Unicode scalar value.
- Every
s64andu64value is a decimal string, including elements of lists and typed arrays:"-9223372036854775808","18446744073709551615". This avoids JavaScript precision loss. - Floating-point values use JSON numbers. Non-finite values (
NaNand infinities) are rejected. - Records use schema field names. Optional fields appear as
nullwhen absent on output; input may omit an optional record field. - Options use the inner value or
null. Results use{"ok":value}or{"err":value}. A result arm with no schema payload uses{"ok":null}or{"err":null}. This is distinct from a payload whose schema is an empty tuple or record; consult the SDK’s result schema rather than assuming that every language unit type becomes a payloadless arm. - A variant case with a payload uses a single-key object, such as
{"Text":"hi"}. A payloadless case is a string, such as"Empty", even in a variant that also has payload cases. All-unit enums likewise use strings. Names and casing come from the schema. - Tuples use fixed-order arrays, lists use arrays, maps use arrays of key-value pairs, and flags use arrays of selected names.
Composite examples
Record:
{ "a": "hello", "b": 1234 }Tuple:
[1234, "hello world", 103]List:
["one", "two", "three"]Map (not an object, even with string keys):
[["key1", 100], ["key2", -2]]Flags:
["get", "put"]Rich values
These schema categories have the same canonical shapes across SDKs. Restrictions narrow accepted values; they do not change these shapes.
| Schema category | Canonical JSON example |
|---|---|
| UUID | "550e8400-e29b-41d4-a716-446655440000" |
| Datetime | "2026-01-02T03:04:05.000000000Z" (UTC RFC 3339) |
| Duration | {"nanoseconds":"1500000000"} |
| Text | {"text":"hello","language":"en"} |
| Binary | {"bytes":"AQID","mimeType":"application/octet-stream"} |
| URL | "https://example.com/file" |
| Path | "/data/file.txt" |
| Quantity | {"mantissa":"12345","scale":2,"unit":"m"} |
Binary bytes use unpadded base64url, not a numeric array. Language and MIME type annotations are optional. Ordinary byte lists remain numeric arrays; choose a rich binary schema explicitly when that is the intended contract.
SDK types to JSON
TypeScript
TypeScript agents declare runtime schemas. An arbitrary interface, type alias, or TypeScript union does not define the transport schema. The table uses the Zod adapter as a concrete example. Valibot, ArkType, and Effect adapters have their own supported constructors; do not assume that every schema library feature is supported.
| Schema | Canonical JSON | Guest value / remarks |
|---|---|---|
z.string(), z.boolean() | "hi", true | String, boolean |
z.number() | 1.5 | number, defaults to f64 |
z.bigint() | "42" | bigint, defaults to unsigned u64 |
s.s64(), s.u64() | "-42", "42" | Explicit signed/unsigned 64-bit integers |
s.s8() … s.s32(), s.u8() … s.u32(), s.f32() | 42 | Exact numeric widths |
z.object({count: z.number()}) | {"count":5} | Record |
z.array(z.string()) | ["hi"] | Array |
z.tuple([z.string(), z.number()]) | ["hi",5] | Tuple |
z.enum(["low", "high"]) | "low" | String enum |
z.union([z.string(), z.number()]) | {"case0":"hi"} or {"case1":5} | Generic zero-based case names; no type-alias prefix |
z.discriminatedUnion("tag", [...]) | {"case0":{"tag":"text","val":"hi"}} | Ordinary branch records; no automatic tag/val unwrapping |
z.optional(T), s.option(T) | inner value or null | Missing value is guest undefined |
z.nullable(T) | inner value or null | Missing value is guest null |
z.record(K,V), z.map(K,V) | [["key",5]] | Map schema; guest object or Map respectively |
s.result(Ok,Err) | {"ok":5} or {"err":"failed"} | Typed result |
z.void() as method return | no result | Not an ordinary JSON null value |
Use s.uint8Array(), s.int8Array(), s.uint16Array(), s.int16Array(), s.uint32Array(), s.int32Array(), s.float32Array(), s.float64Array(), s.bigInt64Array(), and s.bigUint64Array() for typed arrays. All produce lists. The bigint constructors produce decimal-string elements, for example ["-1","0","1"]. s.bytes() aliases the list<u8> constructor, not rich binary.
| Rich schema / export | Category |
|---|---|
s.char(), s.uuid() | Character, UUID |
s.datetime(), s.duration() | Datetime, duration |
s.url(), s.text(), s.binary() | URL, text, binary |
Path(options?), Quantity(options) | Path, quantity schema constructors |
import { method, s } from "@golemcloud/golem-ts-sdk"
import { z } from "zod"
const increment = method({
input: { count: s.u32(), note: z.optional(z.string()) },
returns: s.u64(),
})Unstructured text and binary
Unstructured values carry inline content or a URL reference. Inline content uses the rich text or binary shape, not a bare string or numeric byte list:
| Value | Canonical JSON |
|---|---|
| Inline text | {"inline":{"text":"hi","language":"en"}} |
| Inline binary | {"inline":{"bytes":"AQID","mimeType":"application/octet-stream"}} |
| URL reference (either kind) | {"url":"https://example.com/file"} |
Language and MIME restrictions constrain supported content, not the JSON representation. Guest values use SDK-specific constructors or tags; they are not copies of the canonical JSON objects.
TypeScript
import { method, s } from "@golemcloud/golem-ts-sdk"
import { z } from "zod"
const consume = method({
input: {
text: s.unstructuredText({ languages: ["en"] }),
data: s.unstructuredBinary({ mimeTypes: ["application/octet-stream"] }),
},
returns: z.void(),
})Guest text is {tag:'inline', val:'hi', languageCode:'en'} or {tag:'url', val:'https://example.com/file'}. Guest binary uses val: Uint8Array and optional mimeType. Omit restrictions to accept any language/MIME type.
Multimodal values
A multimodal value is an ordered list of named variant cases. Each occurrence carries one of the declared payload types. Cases, casing, and guest wrappers depend on the SDK; there is no single set of wrapper types shared by every language.
TypeScript
import { method, s } from "@golemcloud/golem-ts-sdk"
import { z } from "zod"
const analyze = method({
input: {
content: s.multimodal([
{ name: "text", schema: s.unstructuredText() },
{ name: "image", schema: s.unstructuredBinary() },
]),
},
returns: z.void(),
})Guest arrays use an outer {tag, value} wrapper, for example [{tag:'text', value:{tag:'inline', val:'hi'}}]. Canonical JSON instead uses [{"text":{"inline":{"text":"hi"}}}]. Use runtime schemas to declare custom cases too.
Values outside this JSON reference
Secrets, quota tokens, and permission cards are host-managed capabilities, not user-constructible JSON values. Streams and futures do not have a canonical JSON transport on this page. Use the dedicated secrets, quotas, and durable streams guides for the supported APIs and boundaries.