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.
Encode
The Encode macro generates JSON encoding methods with cycle detection and object identity tracking. This enables encoding of complex object graphs
including circular references.
Generated Methods
| Type | Generated Code | Description |
|---|---|---|
| Class | classNameEncode(value, keepMetadata?) + static encode(value, keepMetadata?) | Standalone function + static wrapper method |
| Enum | enumNameEncode(value), enumNameEncodeWithContext | Standalone functions |
| Interface | interfaceNameEncode(value), etc. | Standalone functions |
| Type Alias | typeNameEncode(value), etc. | Standalone functions |
Cycle Detection Protocol
The generated code handles circular references using __id and __ref markers:
{
"__type": "User",
"__id": 1,
"name": "Alice",
"friend": { "__ref": 2 } // Reference to object with __id: 2
}When an object is encoded:
- Check if it’s already been encoded (has an
__id) - If so, return
{ "__ref": existingId }instead - Otherwise, register the object and encode its fields
Type-Specific Encoding
| Type | Encoding Strategy |
|---|---|
| Primitives | Direct value |
Date | toISOString() |
| Arrays | For primitive-like element types, pass through; for Date/`Date |
Map<K,V> | For primitive-like values, Object.fromEntries(map.entries()); for Date/`Date |
Set<T> | Convert to array; element handling matches Array<T> |
Record<K,V> | For primitive-like values, pass through; for Date/`Date |
Wrappers (Partial<T>, Pick<T,K>, …) | Encode based on the inner type T |
| Nullable | Include null explicitly; non-null values follow the inner type’s strategy |
| Objects | Call the type’s nameEncodeWithContext function |
Note: which strategy applies is resolved statically from the field’s declared TypeScript type at expansion time: there is no runtime feature detection.
Field-Level Options
The @endec decorator supports:
skip/skipEncoding- Exclude field from encodingrename = "jsonKey"- Use different JSON property nameflatten- Merge nested object’s fields into parent
Example
Before (Your Code)
/** @derive(Encode) */
class User {
id: number;
/** @endec({ rename: "userName" }) */
name: string;
/** @endec({ skipEncoding: true }) */
password: string;
/** @endec({ flatten: true }) */
metadata: UserMetadata;
} After (Generated)
import { EncodeContext } from '@macroforge/core/endec';
class User {
id: number;
name: string;
password: string;
metadata: UserMetadata;
/** Encodes a value to a JSON string.
@param value - The value to encode
@returns JSON string representation with cycle detection metadata */
static encode(value: User, keepMetadata?: boolean): string {
return userEncode(value, keepMetadata);
}
/** @internal Encodes with an existing context for nested/cyclic object graphs.
@param value - The value to encode
@param ctx - The encoding context */
static encodeWithContext(value: User, ctx: __mf_EncodeContext): Record<string, unknown> {
return userEncodeWithContext(value, ctx);
}
}
/** Encodes a value to a JSON string.
@param value - The value to encode
@returns JSON string representation with cycle detection metadata */ export function userEncode(
value: User,
keepMetadata?: boolean
): string {
const ctx = __mf_EncodeContext.create();
const __raw = userEncodeWithContext(value, ctx);
if (keepMetadata) return JSON.stringify(__raw);
return JSON.stringify(__raw, (key, val) => key === "__type" || key === "__id" ? undefined : val);
} /** @internal Encodes with an existing context for nested/cyclic object graphs.
@param value - The value to encode
@param ctx - The encoding context */
export function userEncodeWithContext(
value: User,
ctx: EncodeContext
): Record<string, unknown> {
const existingId = ctx.getId(value);
if (existingId !== undefined) {
return { __ref: existingId };
}
const __id = ctx.register(value);
const result: Record<string, unknown> = { __type: 'User', __id };
result['id'] = value.id;
result['userName'] = value.name;
{
const __flattened = userMetadataEncodeWithContext(value.metadata, ctx);
const { __type: _, __id: __, ...rest } = __flattened as any; // tag field name is configurable via `tag` option
Object.assign(result, rest);
}
return result;
}Required Import
The generated code automatically imports EncodeContext from @macroforge/core/endec.