whoa. I just found a way to properly infer recursive types in z.object() — no casting, no z.lazy(), no scopes/registries, no special syntax. i've been trying to do this for literally years
here's mutual recursion working
works with z.toJSONSchema 💅
this development kind of negates the raison d'être for `z.interface()`. it has been removed in the most recent betas. i've never been happier to delete 3k LOC.
"but what about key-optional versus value-optional properties?" this was one of two stated reasons for the introduction of z.interface() in the original Zod 4 beta announcement (the other being recursive type support)
at that point I was trying to steer people towards z.interface() so I chose de-emphasized a minor change coming in Zod 4... Zod now treats `.optional()` slightly differently than `.or(z.undefined()` — which means both kinds of optionality can be represented in z.object() 🍾






