# Effect Schema `optionalWith` for Safe JSON Serialization in Magnitude: A Complete Guide

> Learn how Effect Schema's optionalWith safely handles optional values in Magnitude, preventing undefined from leaking into JSON by wrapping with Option and omitting None fields.

- Repository: [Magnitude/magnitude](https://github.com/magnitudedev/magnitude)
- Tags: how-to-guide
- Published: 2026-09-06

---

**Effect Schema's `optionalWith` with `{ as: "Option", exact: true }` is the canonical way to handle optional values in Magnitude, ensuring `undefined` never leaks into JSON by wrapping values in `Option` and omitting `None` fields entirely during encoding.**

Magnitude uses **Effect Schema** to model all data structures that cross system boundaries—API payloads, persisted session state, and CLI tool configurations. The primary challenge is JavaScript's `undefined`, which serializes to invalid JSON. This article explains how Magnitude enforces safe optional serialization through `Schema.optionalWith` and the compile-time `IsSchemaJsonSafe` type guard.

## The Problem with `undefined` in JSON

JSON has no representation for `undefined`. When `JSON.stringify()` encounters `undefined` as an object property value, it either omits the key or converts it to `null`—behavior that destroys type safety and causes unexpected bugs in downstream systems.

Effect Schema provides two approaches for optional fields, with vastly different serialization guarantees:

| Declaration | Decoded Type | JSON Safety |
|-------------|-----------|-------------|
| `Schema.optional(X)` / `Schema.UndefinedOr(X)` | `X \| undefined` | **Unsafe** — `undefined` may escape during encoding |
| `Schema.optionalWith(X, { as: "Option", exact: true })` | `Option<X>` | **Safe** — `None` omits the key; `Some(v)` emits `v` |

Magnitude's codebase exclusively uses the second pattern for any schema that will be serialized to JSON.

## How `optionalWith` Works: Encoding Behavior

When you declare a field with `Schema.optionalWith(Schema.String, { as: "Option", exact: true })`, the resulting TypeScript type is `Option<string>`. During **encoding** via `Schema.encodeSync()`, the transformation is deterministic:

- `Some(value)` → the property is included with `value`
- `None` → the property is **entirely omitted** from the output object

```typescript
import { Schema, Option } from 'effect';

// Safe schema: email is Option<string>
const UserSchema = Schema.Struct({
  id: Schema.String,
  email: Schema.optionalWith(Schema.String, { as: 'Option', exact: true })
});

// Encoding examples
const withEmail = Schema.encodeSync(UserSchema)({
  id: '123',
  email: Option.some('alice@example.com')
});
// → { id: "123", email: "alice@example.com" }

const withoutEmail = Schema.encodeSync(UserSchema)({
  id: '124',
  email: Option.none()
});
// → { id: "124" }  // email key omitted — no undefined

```

This behavior is implemented in Effect Schema's core encoding logic, which Magnitude consumes.

## Compile-Time Enforcement with `IsSchemaJsonSafe`

Magnitude guarantees JSON safety through a custom type guard in [`packages/utils/src/schema/json-safe.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/utils/src/schema/json-safe.ts). The **`IsSchemaJsonSafe<S>`** utility recursively inspects any schema at compile time and rejects schemas that could produce `undefined`.

### Detection Logic

The guard operates in three phases (see lines 13–27 and 123 in [`json-safe.ts`](https://github.com/magnitudedev/magnitude/blob/main/json-safe.ts)):

1. **`IsFieldUnsafe`** checks individual properties: if a field's type includes `undefined` and is not wrapped in `Option`, it's flagged as unsafe
2. **Recursive traversal** inspects nested structs, unions, arrays, and transformation nodes (`from`)
3. **Boolean result**: `IsSchemaJsonSafe<S>` evaluates to `true` only when all branches are safe

If you accidentally use `Schema.optional`, the type checker produces a clear error:

```

__jsonSafeError: Schema contains bare Schema.optional() or Schema.UndefinedOr()

```

This error originates from the type definition at line 123 of [`json-safe.ts`](https://github.com/magnitudedev/magnitude/blob/main/json-safe.ts), forcing immediate correction.

### Example: Unsafe vs. Safe Declarations

```typescript
// ❌ UNSAFE: triggers compile-time error in Magnitude
const BadSchema = Schema.Struct({
  id: Schema.String,
  phone: Schema.optional(Schema.String)  // Schema.optional: X | undefined
});
// Type error: __jsonSafeError...

// ❌ ALSO UNSAFE: UndefinedOr has same problem
const AlsoBad = Schema.Struct({
  id: Schema.String,
  phone: Schema.UndefinedOr(Schema.String)
});

// ✅ SAFE: Option wrapper with exact: true
const GoodSchema = Schema.Struct({
  id: Schema.String,
  phone: Schema.optionalWith(Schema.String, { as: 'Option', exact: true })
});
// Compiles: IsSchemaJsonSafe evaluates to true

```

## Real-World Usage in Magnitude

The Magnitude codebase demonstrates `optionalWith` patterns across multiple packages.

### CLI Tool Arguments ([`packages/vcs/src/tools.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/tools.ts))

Optional CLI parameters like `since`, `glob`, and `diff` use `optionalWith` to represent arguments that may be omitted:

```typescript
// Pattern from tools.ts — optional string parameters
since: Schema.optionalWith(Schema.String, { as: 'Option', exact: true }),
glob: Schema.optionalWith(Schema.String, { as: 'Option', exact: true }),

```

### Session State ([`packages/storage/src/types/session.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/storage/src/types/session.ts))

Persisted UI state uses `Option` for optional fields like `archived` and `sidebarOpen`:

```typescript
// From session.ts — safely serialized to storage
archived: Schema.optionalWith(Schema.Boolean, { as: 'Option', exact: true }),
sidebarOpen: Schema.optionalWith(Schema.Boolean, { as: 'Option', exact: true }),

```

These fields may be `None` if the user hasn't toggled the relevant UI element, and the storage layer safely omits them without `undefined` pollution.

### Provider Data ([`packages/providers/src/magnitude/provider.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/providers/src/magnitude/provider.ts))

Provider-specific extensions use the same pattern for optional nested data:

```typescript
// Pattern from provider.ts — optional provider-specific payload
data: Schema.optionalWith(ProviderDataSchema, { as: 'Option', exact: true }),

```

### Test Coverage ([`packages/utils/src/patch/__tests__/exhaustive.test.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/utils/src/patch/__tests__/exhaustive.test.ts))

The test suite in [`exhaustive.test.ts`](https://github.com/magnitudedev/magnitude/blob/main/exhaustive.test.ts) validates that nested schemas with `optionalWith` fields correctly handle `Some` and `None` cases through multiple encoding/decoding round-trips.

## Working with `Option` Values

To construct and consume `Option` values in your Magnitude code:

```typescript
import { Option, pipe } from 'effect';

// Creating values
const someValue = Option.some("present");
const noValue = Option.none();

// Pattern matching
const result = pipe(
  someValue,
  Option.match({
    onSome: (v) => `Value: ${v}`,
    onNone: () => "No value"
  })
);

// Converting from nullable APIs
const fromNullable = Option.fromNullable(maybeString); // null/undefined → None

```

The `Option` type integrates with Effect's full ecosystem—`Effect.gen`, `pipe`, and `Option.match` provide ergonomic handling without risking `undefined` propagation.

## Summary

- **`Schema.optionalWith(X, { as: "Option", exact: true })`** is Magnitude's required pattern for optional JSON-serializable fields
- **`Option<X>`** encodes to either the wrapped value (`Some`) or complete key omission (`None`)
- **`Schema.optional`** and **`Schema.UndefinedOr`** are compile-time rejected by **`IsSchemaJsonSafe`** in [`packages/utils/src/schema/json-safe.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/utils/src/schema/json-safe.ts)
- The guard recursively validates structs, unions, arrays, and transformations at compile time
- Real usage appears in [`packages/vcs/src/tools.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/vcs/src/tools.ts), [`packages/storage/src/types/session.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/storage/src/types/session.ts), and [`packages/providers/src/magnitude/provider.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/providers/src/magnitude/provider.ts)

## Frequently Asked Questions

### What happens if I forget `exact: true` in `optionalWith`?

Without `exact: true`, `optionalWith` may produce different semantics depending on the Effect Schema version. Magnitude's `IsSchemaJsonSafe` guard requires the **exact** option to ensure consistent behavior. Omitting it can cause the type check to fail or produce unexpected encoding results.

### Can I use `Option` from fp-ts or another library instead?

No. Magnitude's `IsSchemaJsonSafe` specifically recognizes Effect's native `Option` type. Other Option implementations would be treated as unknown objects and likely fail the safety check. Import `Option` from `effect` as shown in the code examples.

### How do I migrate existing `Schema.optional` schemas to the safe pattern?

Replace `Schema.optional(X)` with `Schema.optionalWith(X, { as: 'Option', exact: true })`, then update your value construction from `{ field: undefined }` or omitted keys to `Option.none()` or `Option.some(value)`. The type checker will guide remaining fixes through `__jsonSafeError` diagnostics.

### Does `IsSchemaJsonSafe` impact runtime performance?

No. The check is **purely compile-time** TypeScript type manipulation. It produces no runtime code—only type-level boolean evaluation. Your encoded schemas have zero overhead from the safety verification.