I've faced this problem a few times lately, so I built json-up to handle this with a migration chain. Define versioned steps with Zod schemas, and the library walks your data forward from whatever version it's at, validating at each step with full TypeScript inference.
Zero runtime deps, works with Zod v3 and v4, and three distinct error types for precise failure handling.
Have a poke around!