| 72 | } |
| 73 | |
| 74 | function walk(value: unknown, map: NullWideningMap | undefined): unknown { |
| 75 | if (value === null) { |
| 76 | // Strip only nulls the widening pass synthesized (marked `widened`); keep |
| 77 | // every genuine `.nullable()`/`.nullish()` null and every null the map |
| 78 | // doesn't describe. |
| 79 | return map?.widened ? undefined : null |
| 80 | } |
| 81 | if (typeof value !== 'object' || !map) return value |
| 82 | |
| 83 | if (Array.isArray(value)) { |
| 84 | const { items } = map |
| 85 | if (!items) return value |
| 86 | // Tuple maps (`items: [a, b, …]`) describe each position separately; |
| 87 | // a single `items` map applies to every element. |
| 88 | return Array.isArray(items) |
| 89 | ? value.map((item, index) => walk(item, items[index])) |
| 90 | : value.map((item) => walk(item, items)) |
| 91 | } |
| 92 | |
| 93 | const { properties } = map |
| 94 | if (!properties) return value |
| 95 | const result: Record<string, unknown> = {} |
| 96 | for (const [key, child] of Object.entries(value as Record<string, unknown>)) { |
| 97 | const next = walk(child, properties[key]) |
| 98 | // A synthesized null collapsed to undefined → omit the key so the field |
| 99 | // reads as absent (`key in result === false`), matching how `.optional()` |
| 100 | // treats absence. |
| 101 | if (next === undefined) continue |
| 102 | result[key] = next |
| 103 | } |
| 104 | return result |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Inverse of strict-mode null-widening for structured output. |