Skip to content

Convert JSON to io-ts Types — Numio

Generates io-ts runtime codecs from a JSON sample, preceded by import * as t from "io-ts". Nested objects become their own codecs, declared before the codecs that use them; keys missing from some array items go into t.partial.

Validating an API response at runtime in a TypeScript app built on fp-ts and io-ts.

JSON

IO TS

When to use it

  • Validating an API response at runtime in a TypeScript app built on fp-ts and io-ts.
  • Drafting codecs for a JSON config file that must be checked before use.

How to use

  1. Paste a JSON object; include several array items if some keys are optional.
  2. Read the generated codecs: the root is RootInterface and nested ones are named after their keys (settings becomes Settings).
  3. Copy the code, export the codecs you need and derive types with t.TypeOf.

JSON input

{
  "id": "u_1",
  "name": "Ada",
  "age": 36,
  "nickname": null,
  "settings": { "theme": "dark", "fontSize": 14 },
  "posts": [
    { "title": "Notes", "draft": true },
    { "title": "Engine" }
  ]
}

io-ts codecs

import * as t from "io-ts"

const Settings = t.type({
  theme: t.string,
  fontSize: t.number,
})

const Posts = t.intersection([
  t.type({
    title: t.string,
  }),
  t.partial({
    draft: t.boolean,
  }),
])

const RootInterface = t.type({
  id: t.string,
  name: t.string,
  age: t.number,
  nickname: t.null,
  settings: Settings,
  posts: t.array(Posts),
})

Details and limitations

Generated in the browser by the site's own generator and formatted with Prettier; the page has no settings and makes no server request.

Codecs are emitted dependencies first, so every const is defined before RootInterface references it.

Numbers become t.number (integers and decimals alike). null gives t.null, or t.union([X, t.null]) when other samples have a value; empty arrays give t.array(t.unknown).

Keys missing from some items of an array go into t.partial, combined with the required keys through t.intersection.

FAQ

Does it create TypeScript types too?

Not directly. Add type Root = t.TypeOf<typeof RootInterface> to derive the static type from each codec.

How are optional or nullable fields handled?

A key missing from some array items is placed in t.partial; a key that is null in some items becomes t.union([type, t.null]). Top-level keys of a single object are always required.

What happens with a JSON array at the root?

The items are merged into one RootInterface codec for the item shape, and const RootInterfaceList = t.array(RootInterface) is added for the whole array.

Specifications and references