Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Optional Fields

Handle nullable values with ROptional.

Defining Optional Fields

Use ROptional to wrap any schema type:

// Demonstrates: Optional fields with ROptional and null handling

import { RStruct, RString, ROptional, field, createCodec } from "@grounds/schema";
import type { Static } from "@sinclair/typebox";

// Define a schema with optional fields
// Optional fields use null for absent values (not undefined)
const ProfileSchema = RStruct({
  name: field(0, RString()),
  bio: field(1, ROptional(RString())),
  website: field(2, ROptional(RString())),
});

type Profile = Static<typeof ProfileSchema>;

const codec = createCodec(ProfileSchema);

// Profile with all fields
const fullProfile: Profile = {
  name: "Alice",
  bio: "Software developer",
  website: "https://alice.dev",
};

// Profile with some fields null
const minimalProfile: Profile = {
  name: "Bob",
  bio: null,
  website: null,
};

// Encode and decode both
console.log("Full profile:");
codec
  .encode(fullProfile)
  .andThen((bytes) => codec.decode(bytes))
  .match(
    (decoded) => console.log("  Decoded:", decoded),
    (err) => console.error("  Failed:", err.message),
  );

console.log("\nMinimal profile:");
codec
  .encode(minimalProfile)
  .andThen((bytes) => codec.decode(bytes))
  .match(
    (decoded) => console.log("  Decoded:", decoded),
    (err) => console.error("  Failed:", err.message),
  );

Null Semantics

Grounds uses null for absent values (not undefined):

type Profile = {
  name: string; // required
  bio: string | null; // optional
};

// Valid
const profile: Profile = { name: "Alice", bio: null };

// TypeScript error: undefined is not assignable
const profile: Profile = { name: "Alice", bio: undefined };

Wire Format

Optional fields are encoded as:

  • Present: Normal encoding of the inner value
  • Absent: Encoded as Null type (1 byte)

Nested Optionals

You can nest optionals for complex scenarios:

const Schema = RStruct({
  // Optional array
  tags: field(0, ROptional(RArray(RString()))),

  // Array of optional strings
  notes: field(1, RArray(ROptional(RString()))),
});

Next Steps

Continue to Streaming for incremental encoding.