100% local — your data never leaves your browser

JSON to io-ts — Decode and Get the Type

Turn a JSON sample into io-ts codecs. One codec per object, declared before use, and TypeOf hands you the static type from the same declaration.

Instant Private Zero cookies

JSON input

io-ts output

What this tool does

A JSON sample becomes io-ts codecs, one const per object.

{ "id": 1, "name": "Ada", "address": { "city": "Paris" } }
import * as t from "io-ts";

export const Address = t.type({
  city: t.string,
});

export const Root = t.type({
  id: t.Int,
  name: t.string,
  address: Address,
});

Children are declared first: codecs are runtime values, so one has to exist before another mentions it. An array at the root gets its own codec — export const Users = t.array(UsersItem); — rather than leaving you to write it.

t.Int is branded, and that shows

t.Int is a refinement of t.number carrying a proof of integrality. t.TypeOf<typeof Root> therefore gives { id: Int; … }, not { id: number; … }. An Int goes wherever a number is expected; the reverse needs a decode. If that friction is not worth an integer check in your codebase, t.number is a one-word replacement.

Which of the two you get depends on how the number was written: 3 gives t.Int, 10.0 gives t.number. Until recently 10.0 also gave t.Int, and the codec then rejected 10.5 — the next record. That was a real defect, found while writing these pages and fixed.

What a codec lets through

t.type checks what it knows and ignores the rest. A response with three extra fields decodes cleanly. That is io-ts’s interface semantics, and it is the right default for reading someone else’s API — but if an unexpected key should be an error, wrap the same shape in t.strict, or t.exact to strip it.

Decoding, in one line

import { isRight } from "fp-ts/Either";

const result = Root.decode(payload);
if (isRight(result)) { /* result.right is typed */ }

PathReporter.report(result) turns a failure into readable paths — worth having on hand the first time a codec refuses a payload you thought was fine.

Everything is required

Every key from the sample lands in t.type, which means required. io-ts writes optionality as a separate t.partial, joined with t.intersection. Splitting your keys between the two halves is a statement about the API; one document cannot make it for you.

Private by design

Everything runs locally in your browser with JavaScript. Your data is never uploaded, which makes the tool safe for sensitive content, and it keeps working offline.

Frequently asked questions

Does `t.type` reject extra properties?
No. `t.type` validates the properties it knows and lets the rest through, which is what io-ts calls an interface codec. `t.exact` strips the extras, `t.strict` refuses them. The generator emits `t.type` because a sample cannot tell you which of the three you want — that is a decision about your API.
Why does `t.TypeOf` give me `Int` rather than `number`?
`t.Int` is a branded type: an `Int` is a `number` carrying a proof that it was validated as an integer. Assigning an `Int` to a `number` works; the other way round needs a decode. If you would rather have plain numbers everywhere, replace `t.Int` with `t.number` and lose the integer check.
Is every field required?
Yes, and that is what one sample supports. io-ts spells optionality with `t.partial`, usually combined as `t.intersection([t.type({…}), t.partial({…})])`. Deciding which keys belong in the partial half means knowing the API, not reading one document — so the generator leaves the intersection to you.

Related converters