API Stability Notice

Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.

Field Options

Field options go in a @serde(...) decorator on the field.

/** @derive(Serialize, Deserialize) */
class User {
  /** @serde(rename: "user_name") */
  name: string;

  /** @serde(skip) */
  cachedAvatar?: Blob;

  /** @serde(default: "\"unknown\"") */
  status: string;
}

Reference

OptionFormEffect
skipflagOmit from both serialization and deserialization
skipSerializingflagOmit from output only
skipDeserializingflagIgnore in input only
renamerename: "key"Use a different JSON key
defaultflagUse the type’s default when the key is missing
defaultdefault: "expr"Use the given expression when the key is missing
flattenflagInline the nested object’s fields into the parent
serializeWithserializeWith: "fn"Call your function instead of the generated serializer
deserializeWithdeserializeWith: "fn"Call your function instead of the generated deserializer
formatformat: "decimal"Serialize a number as a string
validatevalidate: [...]Run validators

skip and its variants

skip is symmetric. When you need asymmetry — a server-computed field that should be read but never written, or a secret that should be written but never echoed — use the directional forms.

A skipped-on-deserialize field needs a value from somewhere, so pair it with default.

default

The flag form falls back to the type’s natural default. The expression form takes a string containing the expression to evaluate:

/** @serde(default) */
count: number;              // → 0 when missing

/** @serde(default: "\"pending\"") */
status: string;             // → "pending" when missing

flatten

Lifts a nested object’s fields into the parent’s JSON:

class Meta { createdAt: string; }

/** @derive(Serialize) */
class Post {
  title: string;
  /** @serde(flatten) */
  meta: Meta;
}
{ "title": "Hello", "createdAt": "2026-01-01" }

Custom serializers

serializeWith / deserializeWith hand a single field to your own function — the escape hatch for a shape the generated code can’t express. For a type you use repeatedly across many classes, prefer foreign types, which configure the behavior once globally.

format: "decimal"

Serializes a number as a string, preserving precision for values that would lose it as a JSON number — monetary amounts, large IDs:

/** @serde(format: "decimal") */
balance: number;
{ "balance": "1234.56" }