io-ts codec decode and encode: working versions (tested 2026-10-16)
Define io-ts runtime codecs for an object, optional fields, unions, arrays and a branded type, then decode unknown input, report errors with PathReporter and encode back to plain JSON offline.
passed6 passed · 0 failed2026-10-16 20:33 UTC- Status
- passed
- Last passed
- 2026-10-16 20:33 UTC
- Tests
- 6 / 6
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions4
io-ts@2.2.22fp-ts@2.16.11@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38962988948
The code
Exactly the files that passed. Your agent can pull them with get_recipe("io-ts-codec-decode") (install), or see them on GitHub.
package.json
{
"name": "io-ts-codec-decode",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"io-ts": "2.2.22",
"fp-ts": "2.16.11"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/codec.ts
import * as t from "io-ts";
import { PathReporter } from "io-ts/PathReporter";
import type { Either } from "fp-ts/Either";
export interface PositiveIntBrand {
readonly PositiveInt: unique symbol;
}
export const PositiveInt = t.brand(
t.number,
(n): n is t.Branded<number, PositiveIntBrand> =>
Number.isInteger(n) && n > 0,
"PositiveInt"
);
export type PositiveInt = t.BrandC<typeof t.number, PositiveIntBrand>;
export const Role = t.union([t.literal("admin"), t.literal("user")]);
export type Role = t.TypeOf<typeof Role>;
export const User = t.type({
id: t.string,
name: t.string,
role: Role,
tags: t.array(t.string),
meta: t.partial({
age: PositiveInt,
nickname: t.string,
}),
});
export type User = t.TypeOf<typeof User>;
export function decodeUser(input: unknown): Either<t.Errors, User> {
return User.decode(input);
}
export function encodeUser(user: User): unknown {
return User.encode(user);
}
export function explain(input: unknown): string[] {
return PathReporter.report(User.decode(input));
}
test/codec.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { isLeft, isRight } from "fp-ts/Either";
import { decodeUser, encodeUser, explain, type User } from "../src/codec.ts";
const valid: User = {
id: "u1",
name: "Ada",
role: "admin",
tags: ["beta", "early"],
meta: { age: 36 },
};
test("decodes valid unknown input", () => {
const result = decodeUser(valid);
assert.ok(isRight(result));
if (isRight(result)) {
assert.deepEqual(result.right, valid);
}
});
test("treats partial fields as optional", () => {
const result = decodeUser({ ...valid, meta: {} });
assert.ok(isRight(result));
if (isRight(result)) {
assert.deepEqual(result.right.meta, {});
}
});
test("rejects an unknown union member and reports the path", () => {
const bad = { ...valid, role: "owner" };
const result = decodeUser(bad);
assert.ok(isLeft(result));
assert.match(explain(bad).join("\n"), /role/);
});
test("rejects a non-positive branded age", () => {
const bad = { ...valid, meta: { age: -1 } };
const result = decodeUser(bad);
assert.ok(isLeft(result));
assert.match(explain(bad).join("\n"), /PositiveInt/);
});
test("rejects wrong element type inside an array", () => {
const bad = { ...valid, tags: ["ok", 2] };
const result = decodeUser(bad);
assert.ok(isLeft(result));
assert.match(explain(bad).join("\n"), /tags/);
});
test("encodes a valid value back to plain json", () => {
assert.deepEqual(encodeUser(valid), valid);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.