JSON to io-ts Schema Generator
Convert any JSON object into io-ts codec definitions with correct type primitives, t.type for objects, t.array for lists, t.union for nullable fields, and TypeOf type aliases — ready to use for runtime validation of API responses, webhook payloads, and any external data entering your TypeScript application.
Convert any JSON object into io-ts codec definitions with correct type primitives, t.type for objects, t.array for lists, t.union for nullable fields, and TypeOf type aliases — ready to use for runtime validation of API responses, webhook payloads, and any external data entering your TypeScript application.
This tool is designed to provide a seamless experience for developers by handling complex operations directly in your browser with maximum speed and security.
TypeScript's type system is erased at runtime. When you write interface User { id: number; name: string }, that interface provides compile-time guarantees — the TypeScript compiler will catch type errors in code you write. But at runtime, when your application receives a JSON API response and you cast it with as User, TypeScript does nothing to verify the data actually matches the interface. A JSON field that is supposed to be a number but arrives as a string, a required field that is absent, a null value where you expected a string — these pass through silently, and the type errors surface later as runtime exceptions in completely unrelated code. This is the fundamental limitation of TypeScript's structural type system for external data.
io-ts is a runtime type validation library for TypeScript written by Giulio Canti. It takes a different approach: instead of writing TypeScript interfaces that are erased at runtime, you define codecs — values that know how to validate and decode data at runtime AND provide TypeScript types at compile time. A codec like t.type({ id: t.number, name: t.string }) validates that an unknown value is an object with a numeric id and a string name at runtime, and also provides the TypeScript type { id: number; name: string } for compile-time checking. The TypeOf utility extracts the TypeScript type from a codec: type User = t.TypeOf. You write the type once as a codec and get both runtime validation and compile-time types from it.
io-ts is part of the fp-ts ecosystem — it uses Either types for decode results (Right for success, Left for validation errors) rather than throwing exceptions. When you call UserCodec.decode(unknownData), you get back an Either: either the validated User object or a structured description of exactly which fields failed validation and why. This functional error handling is why io-ts is popular in codebases that use fp-ts for functional programming patterns, and why it can feel unfamiliar if you are used to try-catch patterns. For TypeScript applications with strict data boundaries — a Node.js API that receives webhook payloads, a frontend that calls multiple third-party APIs, a service that reads configuration from environment variables — io-ts provides validation guarantees that plain interfaces cannot.
Paste a JSON object and the tool generates an io-ts codec definition for it. Type mapping: JSON strings become t.string, integers and floats become t.number, booleans become t.boolean, JSON arrays become t.array(ElementCodec) with the element codec inferred, and JSON null values become t.union([t.null, t.string]) or the appropriate union with the inferred type. JSON objects become t.type({ field: codec, ... }) nested codec definitions. Each codec gets a TypeOf type alias exported alongside it — type User = t.TypeOf — so you use the codec name as both the runtime validator and the TypeScript type. The generated code is compatible with io-ts 2.x, which is the current maintained version. The import statement uses the standard import * as t from 'io-ts' convention. For nullable fields, the generated union follows the io-ts convention of t.union([t.null, t.string]) with null first — this is the standard nullable pattern in io-ts. For fields that should be optional (present or absent, not just nullable), the distinction from t.union([t.null, t.string]) is important: an optional field uses t.type({ required: t.string }, { optional: t.string }) using the second argument to t.type, or uses t.partial for a fully-optional object structure. The tool generates union for null values — review whether optionality (absence) vs nullability (null value) is the right model for each field. Nested JSON objects generate separate const codec declarations ordered so inner codecs are defined before the outer codec that references them. This is the order io-ts requires — using a codec before it is defined causes a runtime ReferenceError. The output is a complete TypeScript file with the import and all codec and type alias declarations, ready to add to your project's types or validation directory.
1. Paste your JSON into the Input JSON field — use a real API response or data payload sample with all the fields you need to validate. If some fields can be null, include a version with those fields having actual values so the tool can infer the correct inner type for the union. Click Load Example to see a multi-field nested example before using your own data.
2. Click Convert to io-ts Schema — the tool parses every field, maps it to the appropriate io-ts primitive, generates t.union for nullable fields, creates separate codec definitions for nested objects in the correct declaration order, and outputs the complete TypeScript file with import and TypeOf type aliases.
3. Review the generated codec and update the codec name — change the auto-generated name to something meaningful that represents the data structure: UserCodec, WebhookPayloadCodec, StripeEventCodec. The TypeOf alias name is derived from the codec name and becomes the TypeScript type you use in function signatures and variable declarations throughout your codebase.
4. Check the nullable fields carefully — the tool generates t.union([t.null, t.string]) for JSON null fields. Decide whether null (the value) is the right model or whether the field should be optional (absent from the object) using t.partial or the second argument to t.type. This distinction matters: t.union catches the case where the field is present but null; t.partial catches the case where the field is missing from the object entirely. Real API responses often have both behaviors and they require different codec patterns.
5. Add io-ts to your project and use the codec — run npm install io-ts fp-ts (io-ts requires fp-ts as a peer dependency). In your application code: import { UserCodec } from './codecs/user'; import { isRight } from 'fp-ts/Either'; const result = UserCodec.decode(apiResponseData); if (isRight(result)) { const user = result.right; } else { console.error('Validation failed:', result.left); }.
The specific problem io-ts solves becomes most visible when something changes in an API you do not control. A third-party API silently changes a field type — a status field that was always a string is now sometimes a number. A webhook provider adds a new required field and removes an old one. A microservice in your own organization ships a breaking change without updating the shared type definitions first. In a TypeScript codebase using plain interfaces with as casts, these changes produce runtime exceptions that appear in completely wrong places — a downstream function receives bad data, crashes with a cryptic error, and the actual source of the problem (the API response not matching the interface) is two steps away in the callstack. With io-ts, the validation failure is explicit and happens at the data entry point: UserCodec.decode(apiResponse) returns Left(errors) with a description of exactly which field is wrong, and you handle it there. Writing io-ts codecs by hand is slower than writing TypeScript interfaces. A t.type with 15 fields, some of which are nested objects or arrays, takes significantly longer to write than the equivalent interface — every field needs to use the correct io-ts primitive, nullable fields need the union syntax, and nested types need their own codec declarations. For an application with many API boundaries, that adds up. This tool generates the codec structure from a JSON sample so you get the 90% scaffold immediately and only need to make the semantic decisions: which nullable fields should use t.partial vs t.union, whether any string fields should use t.literal for specific allowed values, and whether the nested objects are truly codecs of their own or should be inlined. There is also a documentation value. The io-ts codec is an explicit, running description of what your code expects from an external data source. Unlike TypeScript interfaces which are invisible at runtime, codecs are values — they can be inspected, logged, tested, and passed to helper functions that generate documentation or OpenAPI schemas. A team that uses io-ts consistently has codecs that serve as both the validation layer and the living documentation of API contracts across the codebase.
io-ts 2.x compatible — generated codecs use the current io-ts 2.x API with import * as t from 'io-ts' and standard t.type t.string t.number primitives
TypeOf aliases included — every codec gets a corresponding type User = t.TypeOf alias so the codec serves as both the runtime validator and the TypeScript compile-time type
Correct nullable union syntax — JSON null fields generate t.union([t.null
t.string]) following io-ts nullable conventions with null as the first union member
Nested codec decomposition — separate const codec declarations are generated for nested JSON objects in the correct order so inner codecs are defined before the outer codec that references them preventing ReferenceError at runtime
t.array for JSON arrays — array fields generate t.array(ElementCodec) with the element codec correctly inferred from the array contents
Usage comment included — the output includes a commented example showing how to call decode with fp-ts isRight for the most common validation pattern
100% browser-based — your JSON data never leaves your machine which matters when payloads contain authentication tokens sensitive user data or internal API structures
Instant generation — type inference and codec generation runs locally with no server round-trip
Validating external API response bodies at runtime to catch breaking changes before they propagate through the application
Validating webhook payloads from third-party services (Stripe GitHub Slack) where the payload structure can change without warning
Validating environment variable parsing results to ensure configuration values have the expected types at application startup
Building typed API clients where the response codec serves as both runtime validation and TypeScript type definition
Validating user input from form submissions or external data sources before processing in functional TypeScript applications
Generating codec scaffolds for io-ts codebases that use fp-ts for functional programming patterns
Creating validation layers for microservice boundaries where one service calls another and needs to validate the response structure
Learning io-ts codec syntax by seeing generated code for different JSON structures before writing custom codecs
Example Input
{
"id": 1,
"name": "Priya Singh",
"email": "priya@learnhubly.com",
"isActive": true,
"score": 98.5,
"tags": ["developer", "admin"],
"profile": {
"bio": "Principal Software Engineer",
"skills": ["Go", "React", "TypeScript"]
},
"deletedAt": null
}Example Output
import * as t from 'io-ts';
const ProfileCodec = t.type({
bio: t.string,
skills: t.array(t.string),
});
type Profile = t.TypeOf;
const UserCodec = t.type({
id: t.number,
name: t.string,
email: t.string,
isActive: t.boolean,
score: t.number,
tags: t.array(t.string),
profile: ProfileCodec,
deletedAt: t.union([t.null, t.string]),
});
type User = t.TypeOf;
// Usage:
// import { isRight } from 'fp-ts/Either';
// const result = UserCodec.decode(apiData);
// if (isRight(result)) {
// const user: User = result.right;
// } else {
// // result.left contains validation errors
// }Invalid JSON Input: The tool requires valid JSON before generating io-ts codecs. Syntax errors in the JSON — missing commas, unquoted keys, trailing commas, single-quoted strings — prevent conversion. Use the JSON Formatter and Validator tool to fix any issues first, then retry the conversion.
Complex Nesting Requires Manual Review: Deeply nested JSON generates deeply nested codec compositions. The tool creates separate codec declarations for each nested object level, but for very complex structures with 5+ levels of nesting or polymorphic fields (a field that can be one of multiple object shapes), the generated codec is a starting scaffold that needs manual refinement. Check each nested codec name and rename auto-generated names to domain-meaningful names before using the code in production.
fp-ts Peer Dependency Not Installed: io-ts requires fp-ts as a peer dependency. Running npm install io-ts without also installing fp-ts will cause import errors at runtime when io-ts tries to use fp-ts internals. Install both: npm install io-ts fp-ts. Check your package.json to verify both are listed under dependencies. The specific fp-ts version compatible with your io-ts version is listed in io-ts's package.json under peerDependencies.
t.union vs t.partial Confusion for Optional Fields: The generated codec uses t.union([t.null, t.string]) for JSON null fields. This handles the case where a field is present in the JSON but its value is null. It does not handle the case where the field is completely absent from the JSON object. If your API sometimes omits fields entirely (not present, not null — just absent), you need t.partial for those fields or use the two-argument form of t.type: t.type({ required: t.string }, { optional: t.union([t.null, t.string]) }). Running UserCodec.decode() on an object missing a t.union field will return Left with a 'missing field' error. Decide which fields are absent-optional vs null-optional based on your API's actual behavior.
Large Payloads Generating Long Codec Files: Very large JSON objects with many fields generate correspondingly large codec files. This is correct and expected — io-ts codecs are verbose by design. For codecs that become unwieldy, consider splitting nested codecs into separate files organized by domain: profile-codec.ts, address-codec.ts, imported and composed in the main codec file. The TypeScript module system handles codec composition cleanly — import ProfileCodec from './profile-codec' and use it directly in the parent t.type call.
Using io-ts for all TypeScript types instead of only at external data boundaries
Fix: io-ts is the right tool for validating data that crosses a trust boundary — an HTTP response body, a webhook payload, a message queue message, a form submission, a configuration file read from disk. It is not the right tool for TypeScript types that exist entirely within your application code. Internal function parameters, component props, state shapes — these should use plain TypeScript interfaces or types. They are already type-checked at compile time and do not need runtime validation overhead. Over-applying io-ts makes the codebase verbose without benefit. The rule of thumb is: use io-ts at the edges of your application where untyped data enters, use plain TypeScript types everywhere inside it.
Ignoring the Left result from decode() and treating all decodes as successful
Fix: UserCodec.decode(data) always returns an Either. If you access result.right without checking isRight(result) first, you will get undefined when decoding fails and your code will proceed with bad data. The whole point of io-ts is that validation failures are values you must handle explicitly — not exceptions you can catch and ignore. Handle both cases: if (isRight(result)) { handle(result.right) } else { logErrors(result.left) }. The pipe and fold utilities from fp-ts make this more ergonomic: pipe(result, fold(handleError, handleSuccess)). If you find yourself always assuming Right, plain TypeScript interfaces with as casts provide the same result with less code — the explicit error handling is what makes io-ts worth the verbosity.
Using t.interface instead of t.type (io-ts 1.x vs 2.x)
Fix: io-ts 1.x used t.interface() to define object codecs. io-ts 2.x replaced this with t.type(). If you are following older io-ts tutorials or documentation and see t.interface being used, that API is deprecated in io-ts 2.x. Use t.type() for required fields and t.partial() for optional fields. The generated code from this tool uses t.type() which is correct for io-ts 2.x. If you see type errors from the generated code mentioning t.interface is not a function, verify you are on io-ts version 2.x: check package.json for io-ts version 2.x.x.
Not using t.partial for objects where all fields are optional
Fix: t.type requires all fields to be present in the decoded data — a missing field causes a validation failure. t.partial makes all fields optional — each field can be present or absent. If your JSON structure has an object where all fields might be absent (user preferences that the user may or may not have configured, optional metadata attached to an event), use t.partial instead of t.type. For objects with some required and some optional fields, use intersection: t.intersection([t.type({ requiredField: t.string }), t.partial({ optionalField: t.string })]). The generated code uses t.type for all objects — if you know some fields are genuinely optional, update the relevant codec to use t.partial or the intersection pattern.
Expecting io-ts to validate business rules, not just structural types
Fix: io-ts validates structural type conformance — it checks that a value is a string, a number, an object with specific fields of specific types. It does not validate business rules — it will not check that an email field contains a valid email format, that a number field is positive, or that a date field is not in the past. For those validations, you need additional custom codecs. io-ts provides t.brand for creating branded types with runtime refinements: const EmailBrand = t.brand(t.string, (s): s is t.Branded => /^.+@.+\..+$/.test(s), 'Email'). This creates an Email type that only allows strings matching the pattern. The generated codec covers structure — add branded types for business rule validation on fields that require it.
JSON Cheatsheet
Interactive reference guide for valid JSON syntax, data types, parsing/stringifying methods, schema validation rules, and language integrations.
Regex Cheatsheet
Interactive guide to Regex anchors, character classes, quantifiers, lookarounds, capturing groups, and search flags.
HTTP Headers Cheatsheet
Complete guide to standard and security HTTP headers including Authorization, CORS control, caching policies, and CSP directives.
SQL Cheatsheet
Complete guide to SQL statements including SELECT queries, WHERE filters, aggregate functions, JOIN types, and DDL commands.
Does it support io-ts 2.0?
Yes. The generated code targets io-ts 2.x, which has been the stable API since 2019. It uses t.type for object codecs, t.string, t.number, t.boolean, t.null, t.array, and t.union for all type primitives — all standard io-ts 2.x API. If you are on io-ts 1.x, the main difference is that t.interface() was the object codec constructor in 1.x (now deprecated in favor of t.type()). The TypeOf utility and the Either-based decode result are consistent across 1.x and 2.x.
Can I customize the schema names?
Yes. The generated codec names are placeholders — rename the const declarations to meaningful domain names before using them. UserCodec, ProfileCodec, WebhookPayloadCodec, StripeEventCodec — whatever reflects your domain. The TypeOf alias uses the codec name, so renaming UserCodec to GitHubRepoCodec also means renaming the type alias to type GitHubRepo = t.TypeOf. Keep the Codec suffix on the const name and drop it on the type alias as a convention — it makes it clear in your code which identifiers are io-ts codecs (runtime values) and which are TypeScript types (compile-time only).
Is it safe for sensitive data?
Yes — all processing runs in your browser. The JSON you paste, including API responses that may contain authentication tokens, user PII, or internal endpoint paths, never leaves your machine and is never transmitted anywhere. This is relevant if you are generating codecs for internal APIs or for services where the response structure itself is sensitive.
What is the difference between io-ts and Zod?
Both provide runtime validation with TypeScript type inference, but they have different philosophies and APIs. io-ts is built on fp-ts and uses functional programming patterns — decode returns an Either, errors are structural data types, and composing codecs uses functional combinators. Zod has a more ergonomic API designed to feel like TypeScript type declarations — z.object({ name: z.string() }) — and parse throws errors rather than returning Either. Zod is easier to get started with and has become the default choice for many new TypeScript projects. io-ts is the right choice if you are already using fp-ts and want validation that fits the functional programming model throughout your codebase. If you are not using fp-ts and just need runtime validation, Zod's API is significantly more approachable.
How do I handle a field that can be one of several different types?
Use t.union with all the possible types: t.union([t.string, t.number]) for a field that can be either a string or a number. For a discriminated union (an object where a type field determines which shape the object takes), use t.union with multiple t.type codecs: t.union([t.type({ kind: t.literal('text'), content: t.string }), t.type({ kind: t.literal('image'), url: t.string })]). The t.literal codec matches a specific value rather than any value of a type — t.literal('active') only matches the string 'active', not any string. This combination of t.union and t.literal is io-ts's approach to discriminated union validation.
How do I decode an array at the top level instead of a single object?
Wrap the generated codec in t.array: const UserListCodec = t.array(UserCodec). Then decode with UserListCodec.decode(apiData). If the API returns a paginated response with the array wrapped in an object like { data: [...], total: 100 }, generate the outer wrapper codec from the full response — the tool will create a codec with a t.array(ItemCodec) field for the array and t.number for total.
What is a branded type and when do I need one?
A branded type in io-ts is a refinement on a base type that adds a custom validation predicate. t.string validates that a value is any string. A branded Email type validates that a value is a string AND matches an email pattern, AND TypeScript tracks that the value has been validated through its type. Use branded types when a plain structural type is not specific enough — an email field that should only accept valid email formats, a user ID field that should only accept UUIDs, a positive integer field that should reject negative values. Branded types let you express these constraints in the type system so that functions that accept an Email cannot accidentally receive an arbitrary unvalidated string. The generated code uses plain t.string and t.number — replace with branded types for fields that require format validation.
Should I use io-ts or just TypeScript interfaces with type assertions?
It depends entirely on how much you trust the external data sources your application consumes. If you control both the API and the client, share types via an npm package or monorepo, and deploy them together — TypeScript interfaces are probably sufficient. If your application consumes third-party APIs, public webhooks, user-provided data, or any source that can change independently of your TypeScript code — io-ts (or Zod) provides genuine safety that interfaces cannot. The as cast is a lie: const user = responseData as User compiles and runs regardless of whether responseData actually matches User. UserCodec.decode(responseData) tells you whether it actually does. The question is how much you care about finding out when it does not.
How to Convert JSON to Dart Classes for Flutter – Complete In-Depth Guide (2026)
How to convert JSON to Dart classes for Flutter in 2026. In-depth guide covering Dart model best practices, null safety, immutable classes, code generation with json_serializable & freezed, Flutter integration with Riverpod, and real-world architecture tips from a Principal Software Engineer with 15+ years experience.
What is JSON? How to Format, Validate & Use It (Complete Guide 2026)
What is JSON? How to Format, Validate & Use It (Complete Guide 2026). In-depth explanation of JSON syntax, real-world use cases, formatting best practices, common mistakes, advantages, disadvantages, and expert tips from a Principal Software Engineer with 15+ years experience.
How to validate JSON online (step-by-step guide)
Invalid JSON can break your application. Follow this guide to quickly validate and fix your JSON data.
Recent Activity
No recent activity