Split View: TypeScript 데이터 검증 2026 — Zod·Valibot·ArkType·TypeBox·Effect Schema 심층 비교 (AI 도구 호출 시대의 런타임 검증)
TypeScript 데이터 검증 2026 — Zod·Valibot·ArkType·TypeBox·Effect Schema 심층 비교 (AI 도구 호출 시대의 런타임 검증)
프롤로그 — 모든 API 경계가 스키마를 요구하는 시대
2022년 TypeScript 프로젝트에서 "런타임 검증 뭐 써?"라고 물으면 답은 거의 하나였다. Zod. 그 전엔 Joi, Yup, 또는 직접 짠 타입 가드.
2026년의 같은 질문엔 후보가 다섯이다. 그 사이에 일이 너무 많이 일어났다.
- tRPC가 표준이 됐다 — 모든 절차 입력에 Zod 스키마를 박아 넣는 패턴이 풀스택 TS의 기본 문법이 됐다. 스키마 = API 경계.
- React Hook Form + zodResolver 패턴이 폼의 사실상 표준 — 폼 검증 = 같은 스키마.
- AI 도구 호출(function calling, tool use)이 JSON Schema를 강제 — OpenAI·Anthropic·Gemini 모두 도구 정의를 JSON Schema로 받는다. TS 스키마를 JSON Schema로 못 내보내면 곤란해졌다.
- OpenAPI 자동 생성이 다시 핫함 — Hono·tRPC-openapi·
@asteasolutions/zod-to-openapi·TypeBox의 빌트인 인터롭. 한 스키마로 클라이언트·서버·문서가 동시에 빠진다. - 번들 크기 = 돈 — 엣지 런타임·Cloudflare Workers에 올라가는 코드가 늘었다. Zod 1MB는 옛말이라도 KB 단위로 따지는 진영이 자랐다.
이 흐름이 다섯 진영을 만들었다.
- Zod 4 — 사실상 표준. 4.x에서 트리 셰이킹·파서 재작성으로 약점을 정통으로 쳤다.
- Valibot — 함수형 모듈러 API. 쓰는 만큼만 번들에 들어간다. 프런트엔드 폼·엣지에서 강함.
- ArkType — TS 문자열을 스키마로.
type({ email: 'string.email' })같은 식. TS 추론과 100% 호환이 무기. - TypeBox — 만든 스키마가 곧 JSON Schema. Fastify·OpenAPI·AI 도구 호출과 직결.
- Effect Schema — Effect 생태계의 검증 도구. 타입드 에러·합성 가능한 변환·양방향 인코딩이 무기.
- Yup / Joi / Superstruct — 살아 있지만 신규 채택은 점점 줄어든다.
이 글은 2026년 5월 기준 이 라이브러리들을 직접 비교한다. 번들·추론·JSON Schema·비동기·OpenAPI·런타임 비용·생태계의 7축으로. 그리고 같은 User 스키마를 5개 라이브러리로 나란히 짠다.
1장 · 풍경 — 데이터 검증 라이브러리 지도
먼저 분류부터. 모든 게 같은 자리에 있지 않다.
| 분류 | 대표 도구 | 한 줄 요약 |
|---|---|---|
| 객체 빌더 (체이닝) | Zod, Yup, Joi | z.string().email() 식. 가장 친숙. |
| 모듈러 함수 | Valibot | string([email()]). 트리 셰이킹 친화. |
| TS 문법을 스키마로 | ArkType | TS 타입 표현을 그대로 런타임에. |
| JSON Schema 네이티브 | TypeBox | 만든 게 곧 JSON Schema. |
| 함수형 + Effect | Effect Schema | 타입드 에러, 양방향 인코딩. |
| 가벼운 어서션 | Superstruct, io-ts | 작고 합성 가능, 약간 옛 스타일. |
이 글의 초점은 굵게 갈리는 다섯 — Zod·Valibot·ArkType·TypeBox·Effect Schema — 다. Yup·Joi·Superstruct는 7장에서 짧게 정리한다.
왜 이 다섯인가
- Zod 4 — 신규 TypeScript 프로젝트의 기본값. tRPC·Next.js·React Hook Form 어디서나 등장. 2024년 발표된 Zod 4가 2025~2026 사이 안정화되면서 트리 셰이킹 약점이 크게 줄었다.
- Valibot — 2024년부터 본격 성장. 함수 단위 import로 사용 안 한 코드가 번들에서 떨어진다. 프런트엔드·엣지에서 점유율을 빠르게 가져갔다.
- ArkType — TS 표현을 그대로 받는 사고 전환이 매력. v2 안정화 이후 신규 프로젝트의 한 갈래.
- TypeBox — Fastify 권장, AJV 결합으로 가장 빠른 런타임 중 하나. AI 도구 호출에서 JSON Schema를 직접 받아야 할 때 첫 번째 선택지.
- Effect Schema — Effect 풀스택을 가는 팀의 검증 도구. Effect를 안 쓰면 굳이 고를 이유는 적다.
2장 · 7축으로 보는 비교 매트릭스
상세 분석으로 들어가기 전, 한눈에 보는 큰 그림.
| 축 | Zod 4 | Valibot | ArkType | TypeBox | Effect Schema |
|---|---|---|---|---|---|
| 번들 (코어) | 중간 | 매우 작음 (모듈러) | 작음 | 매우 작음 | 큼(Effect 포함) |
| TS 추론 품질 | 매우 좋음 | 매우 좋음 | 최상 (TS 표현 그대로) | 좋음 | 매우 좋음 |
| JSON Schema 익스포트 | 외부 패키지 필요 | 외부 패키지 필요 | 빌트인 | 빌트인 (정의 자체가 JSON Schema) | 빌트인 |
| 비동기 검증 | O | O | 부분 | X (동기 중심) | O |
| OpenAPI 익스포트 | zod-to-openapi 등 | @valibot/to-json-schema | 빌트인 | 빌트인 (Fastify·AJV) | @effect/schema-openapi |
| 런타임 비용 | 빠름 (4에서 큰 폭 개선) | 매우 빠름 | 빠름 | 가장 빠름 (AJV 컴파일 시) | 중간~빠름 |
| 생태계 | 가장 큼 | 빠르게 성장 | 중간 | API/Fastify 진영 | Effect 사용자 |
이 표만 보고 결정을 내려선 안 된다. 다음 장부터 각 도구를 정확히 무엇이 되고 안 되는지 짚는다.
3장 · Zod — 표준이 된 이유, 그리고 Zod 4의 반격
Zod는 2020년 등장 이래 사실상 TypeScript 검증의 표준이 됐다. 이유 세 가지.
- 체이닝 API의 친숙함 —
z.string().email().min(5)한 줄로 의도가 드러난다. Joi·Yup에서 온 사람에게 학습 비용이 거의 0. z.infer타입 추론 — 스키마에서 TS 타입을 자동 도출. 스키마 = 타입의 진실의 원천.- 생태계 폭발 — tRPC·
@hookform/resolvers·OpenAI SDK·LangChain·Next.js Server Actions 등 모두 Zod를 일급으로 받는다.
Zod 3 시대의 약점
그래도 Zod는 두 가지 비판을 오래 받았다.
- 번들 크기 — 체이닝 API 특성상 전체 인덱스가 한 번에 불려 와서 트리 셰이킹이 약했다. 단순
z.string()만 써도 수십 KB가 들어옴. - 런타임 비용 — 깊은 객체에서 파싱이 의외로 무거움. 핫 패스(요청당 수천 번 호출)에서 보였다.
Zod 4가 바꾼 것
Zod 4는 2024년 10월 베타 공개, 2025년 본격 GA. 큰 변경 셋.
- 트리 셰이커블 빌드 — 사용하지 않은 검증자는 번들에서 떨어진다.
import { z } from 'zod'그대로 쓰면서 실제 결과는 작아짐. - 파서 재작성 — 인터널을 단순화하고 핫 패스를 최적화. 공식 벤치마크 기준 v3 대비 의미 있는 속도 향상(릴리즈 노트의 수치 참고).
- 확장 분리 —
zod코어와zod/v4, 미니 빌드(zod/v4-mini)가 분리됐다. 후자는 Valibot 수준의 매우 작은 번들을 목표.
Zod로 같은 User 스키마
import { z } from 'zod'
const User = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
name: z.string().min(1),
})
type User = z.infer<typeof User>
const parsed = User.parse({ email: 'foo@example.com', age: 30, name: 'Foo' })
// ^ parsed: User
safeParse로 에러를 값으로 받기.
const result = User.safeParse(unknown)
if (!result.success) {
// result.error is a ZodError; pretty issues at result.error.issues
console.error(result.error.flatten())
}
Zod의 강점 (2026)
- 가장 큰 생태계. 신규 라이브러리·SDK가 거의 자동으로 Zod 어댑터 제공.
- 4.x로 번들·속도 약점이 크게 줄었음.
- 비동기 검증(
z.string().refine(async ...)) 지원이 매끄러움. - 학습 자료가 압도적.
Zod의 약점 (2026)
- 절대적 번들은 Valibot보다 여전히 큼. 엣지 워커에서 한 KB라도 줄이고 싶으면 후보가 아님.
z.discriminatedUnion같은 일부 API 학습 비용이 약간 있음.- JSON Schema 익스포트는 외부 패키지(
@asteasolutions/zod-to-openapi등). 직접 지원은 부분적.
4장 · Valibot — 함수형 모듈러로 번들을 깎다
Valibot은 2023년 말~2024년 사이 폭발적으로 자란 라이브러리다. 핵심 철학 하나.
"기본을 함수로 짜라. 안 쓰는 코드는 번들에 없다."
Zod가 z.string().email().min(5)라면 Valibot은 함수 합성이다.
import * as v from 'valibot'
const User = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
name: v.pipe(v.string(), v.minLength(1)),
})
type User = v.InferOutput<typeof User>
const parsed = v.parse(User, { email: 'foo@example.com', age: 30, name: 'Foo' })
v.pipe(...)로 검증자를 합성하고, v.parse/v.safeParse로 적용한다. 함수가 모두 named export — 안 쓴 건 esbuild·Rollup이 떨어낸다.
번들 차이
공식 사이트와 커뮤니티 벤치마크가 보여 주는 그림.
v.string()한 번만 쓰면 Valibot에서 들어가는 코드는 수백 바이트 수준.- 같은 코드가 Zod 3에서는 수십 KB. Zod 4에서는 큰 폭으로 줄었지만 Valibot보다는 여전히 큼.
- 큰 스키마(여러 검증자)를 모두 쓰면 차이는 줄어들지만, 작게 시작할수록 Valibot이 유리.
Valibot의 BaseSchema API
Valibot 1.0 GA(2024) 이후 API가 안정화됐다. 주요 함수.
v.object,v.array,v.tuple,v.union,v.intersect,v.recordv.string,v.number,v.boolean,v.literal,v.date,v.bigintv.pipe(schema, ...actions)— 검증자 합성v.transform(fn),v.brand(name)— 변환·브랜드 타입
비동기 검증은 별도 v.parseAsync/v.safeParseAsync.
Valibot의 강점
- 번들 크기 압도적 — 프런트엔드·엣지 어디서나 가장 작음.
- 타입 추론 품질 —
InferOutput·InferInput양쪽 다 정확. - 함수형이라 합성이 쉬움 — 새 검증자 만드는 비용이 낮음.
- 빠르게 자라는 생태계 — Standard Schema 지원,
@valibot/to-json-schema, React Hook Form@hookform/resolvers/valibot.
Valibot의 약점
- API 학습 비용 — 체이닝에 익숙한 사람에게 처음엔 어색.
- 에러 메시지 사용자화는 약간 더 손이 감.
- Zod 어댑터를 받는 일부 라이브러리에서 Valibot 어댑터는 아직 없거나 늦게 추가됨.
- OpenAPI 익스포트가 Zod만큼 두꺼운 패키지가 없음 — 직접 짜야 할 일이 더 있음.
5장 · ArkType — TypeScript 문법을 그대로 스키마로
ArkType은 2024년에 v2를 GA로 내며 주목받았다. 발상이 다르다.
"TypeScript 표현을 문자열로 받아서, 그대로 런타임 스키마로 만든다."
같은 User 스키마.
import { type } from 'arktype'
const User = type({
email: 'string.email',
age: 'number.integer >= 0',
name: 'string > 0',
})
// type 추론: TS가 정확히 그 표현을 이해
type User = typeof User.infer
const out = User({ email: 'foo@example.com', age: 30, name: 'Foo' })
if (out instanceof type.errors) {
console.error(out.summary)
} else {
// out is the validated value
}
핵심은 'number.integer >= 0' 같은 표현이 TS 컴파일러에 의해 정확한 타입으로 추론된다는 것. ArkType은 자체 미니 DSL을 만들고, 그 DSL을 TS 템플릿 리터럴 타입으로 파싱한다. 결과는 스키마를 손으로 쓴 TS 타입과 100% 일치.
ArkType의 차별점
- TS 표현 그대로 —
'string | number','(string | number)[]>=2'같이 친숙. - 타입 우선 —
typeof User.infer로 즉시 사용. 별도.infer호출 비용 없음(타입 레벨). - JSON Schema 익스포트 빌트인 —
User.toJsonSchema()한 줄. - 런타임 성능 — 컴파일된 검증자 — 스키마 정의 시 내부적으로 검증 함수를 미리 빌드.
ArkType 객체 정의의 다른 모양
문자열만이 아니라 TS 객체 그대로도 받는다.
import { type } from 'arktype'
const Post = type({
id: 'number.integer',
title: 'string > 0',
tags: 'string[]',
author: {
id: 'number',
name: 'string',
},
publishedAt: 'Date',
})
ArkType의 강점
- TS 친화도 최상 — TS를 잘 아는 사람에게 학습 곡선이 거의 0.
- JSON Schema 빌트인 — 외부 패키지 없이 변환.
- 런타임 빠름 — 컴파일된 검증자.
- 함수 호출이 스키마 =
User(value)— API가 직관적.
ArkType의 약점
- 에코시스템이 아직 작다 — tRPC·React Hook Form 어댑터는 있지만 Zod만큼 두껍지 않음.
- 에러 메시지 한국어화·사용자화 자료가 적음.
- 비동기 검증 지원이 부분적 — 동기 중심 설계.
- DSL 문자열에 익숙해져야 함 —
'number.integer >= 0'이 처음엔 마법처럼 느껴짐.
6장 · TypeBox — 만든 게 그대로 JSON Schema
TypeBox는 2021년 등장 이후 Fastify 진영에서 사랑받다가, AI 도구 호출 시대에 들어와 다시 주목받았다.
"내가 만든 스키마가 곧 JSON Schema다. AJV로 컴파일하면 가장 빠른 런타임."
같은 User 스키마.
import { Type, Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
const User = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
name: Type.String({ minLength: 1 }),
})
type User = Static<typeof User>
const ok = Value.Check(User, unknown)
const errors = [...Value.Errors(User, unknown)]
Type.Object({ ... })가 반환하는 값은 JSON Schema 그 자체. JSON.stringify(User)하면 표준 JSON Schema 문서가 나온다.
AJV 결합 — 가장 빠른 런타임
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const ajv = addFormats(new Ajv())
const check = ajv.compile(User)
const ok = check(value) // 컴파일된 함수 직접 호출 — 매우 빠름
AJV는 JSON Schema를 받아서 검증 함수를 V8 친화적인 코드로 빌드한다. 결과는 일반적으로 가장 빠른 런타임 검증 중 하나다.
TypeBox + Fastify
import Fastify from 'fastify'
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>()
app.post('/users', {
schema: { body: User, response: { 200: User } },
}, async (req) => {
// req.body is typed as User automatically
return req.body
})
스키마가 곧 OpenAPI 응답 명세 + 런타임 검증 + 응답 직렬화까지. 한 번 정의로 네 가지 일을 한다.
AI 도구 호출에서 TypeBox
OpenAI·Anthropic·Gemini의 함수/도구 정의는 JSON Schema다.
const GetWeather = Type.Object({
city: Type.String(),
units: Type.Optional(Type.Union([Type.Literal('c'), Type.Literal('f')])),
})
const tool = {
type: 'function',
function: {
name: 'get_weather',
description: '주어진 도시의 현재 날씨',
parameters: GetWeather, // 그대로 JSON Schema
},
}
zod-to-json-schema 같은 변환기를 거치지 않고 직접 사용 가능. 변환 시 잃어버리는 정보(예: format, description)가 없다.
TypeBox의 강점
- JSON Schema 표준 그대로 — OpenAPI·AI 도구 호출·AJV·Ajv 모두 직결.
- AJV로 가장 빠른 런타임 — 핫 패스 검증에서 최적.
- Fastify·Hono·Express 미들웨어 풍부.
- 번들 매우 작음 —
Type.*함수 자체는 가볍다.
TypeBox의 약점
- API가 약간 verbose —
Type.Object({ x: Type.String() })이 길게 느껴짐. - 체이닝의 매력은 없음 —
z.string().email().min(5)같은 짧음이 그리울 수 있음. - 에코시스템이 Fastify 쪽에 치우침 — React 폼 라이브러리 어댑터는 Zod·Valibot보다 적음.
- 비동기 검증은 약함 — JSON Schema 자체가 동기 검증 중심.
7장 · Effect Schema — Effect 생태계의 검증 도구
Effect는 Scala·Haskell 영향을 받은 풀스택 TS 함수형 프레임워크다. Effect Schema는 그 안의 데이터 검증·변환 모듈.
"검증은 양방향 변환이다. 그리고 모든 에러는 타입에 들어간다."
같은 User 스키마.
import { Schema } from 'effect'
const User = Schema.Struct({
email: Schema.String.pipe(Schema.email()),
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
name: Schema.NonEmptyString,
})
type User = Schema.Schema.Type<typeof User>
const decode = Schema.decodeUnknownSync(User)
const out = decode({ email: 'foo@example.com', age: 30, name: 'Foo' })
decodeUnknownSync는 동기 검증. decodeUnknown 또는 decode는 Effect 값을 반환 — Effect 풀스택 안에서 합성 가능.
Effect Schema의 차별점
- 양방향 —
decode↔encode— JSON → 도메인, 도메인 → JSON 둘 다. Date·BigDecimal·Brand 같은 도메인 타입을 자연스럽게 다룸. - 타입드 에러 —
ParseError가 타입에 들어 있어, 에러 처리 누락을 컴파일러가 잡음. - Schema 합성 —
Schema.compose,Schema.transform,Schema.filter로 합성. - JSON Schema 빌트인 —
JSONSchema.make(User)한 줄.
양방향 변환 예시
import { Schema } from 'effect'
// JSON string으로 들어오는 Date를 도메인 Date로
const DateFromString = Schema.transform(Schema.String, Schema.Date, {
decode: (s) => new Date(s),
encode: (d) => d.toISOString(),
})
const Event = Schema.Struct({
name: Schema.NonEmptyString,
at: DateFromString,
})
const decoded = Schema.decodeUnknownSync(Event)({ name: 'Launch', at: '2026-05-14T00:00:00Z' })
// ^ decoded.at is real Date
Effect Schema의 강점
- 양방향 검증·변환의 우아함 — 다른 라이브러리에서 별도로 짜야 할 일이 자연스럽게 풀림.
- 타입드 에러로 에러 처리 누락 차단.
- Effect 풀스택과 합성 — HTTP·DB·캐시·재시도까지 한 모델.
- JSON Schema 빌트인.
Effect Schema의 약점
- Effect를 안 쓰면 학습·번들 비용이 큼 — Effect 코어가 같이 들어옴.
- 체이닝에 비해 코드가 길어 보임 —
.pipe(Schema.email())이 익숙해지기 전까지 어색. - 생태계 — 폼·tRPC 어댑터는 부분적.
- 러닝 커브 — Effect 자체 학습이 필요. 카테고리 이론·함수형 친화가 필요.
8장 · 같은 스키마, 5가지 라이브러리 — User(email + age + name)
5개를 나란히 본다. 같은 의미의 스키마.
8.1 Zod
import { z } from 'zod'
const User = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
name: z.string().min(1),
})
type User = z.infer<typeof User>
8.2 Valibot
import * as v from 'valibot'
const User = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
name: v.pipe(v.string(), v.minLength(1)),
})
type User = v.InferOutput<typeof User>
8.3 ArkType
import { type } from 'arktype'
const User = type({
email: 'string.email',
age: '0 <= number.integer <= 150',
name: 'string > 0',
})
type User = typeof User.infer
8.4 TypeBox
import { Type, Static } from '@sinclair/typebox'
const User = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
name: Type.String({ minLength: 1 }),
})
type User = Static<typeof User>
8.5 Effect Schema
import { Schema } from 'effect'
const User = Schema.Struct({
email: Schema.String.pipe(Schema.email()),
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
name: Schema.NonEmptyString,
})
type User = Schema.Schema.Type<typeof User>
비교 — 같은 스키마, 다른 철학
| 라이브러리 | 줄 수 | 문법 스타일 | 추론 호출 |
|---|---|---|---|
| Zod | 짧음 | 체이닝 | z.infer |
| Valibot | 중간 | 함수 합성 (pipe) | v.InferOutput |
| ArkType | 가장 짧음 | TS DSL 문자열 | typeof X.infer |
| TypeBox | 중간 | Type 빌더 | Static |
| Effect Schema | 중간 | .pipe 체이닝 | Schema.Schema.Type |
읽기 쉬움은 Zod·ArkType이 강하고, 번들·트리 셰이킹은 Valibot·TypeBox가 강하고, 변환·에러 타입까지 챙기면 Effect Schema가 강하다.
9장 · AI 도구 호출 시대 — JSON Schema가 강제된다
2024~2025 사이 가장 큰 흐름. 모든 주요 LLM이 도구 정의를 JSON Schema로 받는다.
9.1 OpenAI function calling
const tools = [{
type: 'function',
function: {
name: 'get_weather',
description: 'Get the current weather in a given city',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['c', 'f'] },
},
required: ['city'],
},
},
}]
parameters는 표준 JSON Schema. 객체 직접 작성하거나, TS 스키마 라이브러리에서 변환해야 한다.
9.2 Anthropic tool use
const tools = [{
name: 'get_weather',
description: 'Get the current weather in a given city',
input_schema: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['c', 'f'] },
},
required: ['city'],
},
}]
input_schema 키만 다를 뿐 같은 모양.
9.3 라이브러리별 JSON Schema 변환
- Zod —
@asteasolutions/zod-to-openapi,zod-to-json-schema, OpenAI SDK가 직접 받기도 함(zodFunction헬퍼). Anthropic SDK도 Zod 직접 지원이 늘었다. - Valibot —
@valibot/to-json-schema공식 패키지. - ArkType —
User.toJsonSchema()빌트인. - TypeBox — 변환 필요 없음. 정의 자체가 JSON Schema.
- Effect Schema —
JSONSchema.make(User)빌트인.
9.4 변환 시 잃어버리는 것
JSON Schema는 TS 표현보다 한정적이다. 변환 시 종종 잃는 것들.
z.transform/v.transform같은 양방향 변환 — JSON Schema는 입력 검증만 표현.z.brand로 만든 브랜드 타입 — 단순string등으로 평탄화.- 세밀한
refine함수 — JSON Schema에 표현 불가능한 검증은 사라짐. - 재귀 스키마 —
$ref처리 라이브러리·LLM 측이 다 잘 받지 않음. 평탄화하거나 깊이 제한이 안전.
9.5 OpenAI structured outputs와 strict 모드
OpenAI 2024 후반에 등장한 structured outputs는 JSON Schema 일부 제약(예: additionalProperties: false, 필수 필드, $ref 미지원 등)을 강제한다. TS 라이브러리가 만드는 JSON Schema가 그 제약을 깨면 OpenAI 측이 거부한다.
실무 팁.
- 도구 호출용 스키마는 가능한 한 단순하게(평탄,
additionalProperties: false). description필드를 적극 활용 — LLM이 의미를 이해하는 가장 좋은 단서.- 재귀·
$ref는 피하거나 LLM 친화적으로 평탄화. - 변환 결과를 한 번 sanity check — 변환된 JSON Schema를 직접 보고 의도와 같은지.
10장 · 7축 상세 비교
10.1 번들 크기
순수 코어를 작은 스키마 하나 정도만 쓰는 가정.
| 라이브러리 | 작은 사용 | 큰 사용 | 비고 |
|---|---|---|---|
| Valibot | 가장 작음 (수백 B) | 작음 | 트리 셰이킹의 정점 |
| TypeBox | 매우 작음 | 작음 | Type.* 자체가 가벼움 |
| ArkType | 작음 | 중간 | 파서가 함께 들어옴 |
| Zod 4 | 중간 (큰 폭 감소) | 중간 | v4-mini는 더 작음 |
| Zod 3 | 큼 | 큼 | 트리 셰이킹 약함 |
| Effect Schema | 큼 | 큼 | Effect 코어 포함 |
| Yup | 중간 | 중간 | |
| Joi | 큼 | 큼 | 브라우저 친화도 낮음 |
10.2 TypeScript 추론 품질
| 라이브러리 | 추론 품질 | 비고 |
|---|---|---|
| ArkType | 최상 | TS 표현 그대로 |
| Zod 4 | 매우 좋음 | z.infer 광범위 |
| Valibot | 매우 좋음 | InferOutput/InferInput |
| Effect Schema | 매우 좋음 | 양방향 타입까지 |
| TypeBox | 좋음 | Static |
| Yup | 보통 | 추론은 약함 |
| Joi | 약함 | 별도 타입 작성 필요 |
10.3 JSON Schema 익스포트
| 라이브러리 | 지원 | 방식 |
|---|---|---|
| TypeBox | 네이티브 | 정의 자체 |
| ArkType | 빌트인 | .toJsonSchema() |
| Effect Schema | 빌트인 | JSONSchema.make() |
| Valibot | 공식 외부 | @valibot/to-json-schema |
| Zod | 외부 | zod-to-json-schema, @asteasolutions/zod-to-openapi |
| Yup | 빈약 | 비공식 변환기 |
| Joi | 없음 | 직접 작성 |
10.4 비동기 검증
| 라이브러리 | 지원 | 패턴 |
|---|---|---|
| Zod | 강함 | z.string().refine(async ...) + parseAsync |
| Valibot | 강함 | parseAsync/safeParseAsync |
| Effect Schema | 강함 | Effect 자체가 비동기 |
| ArkType | 부분 | 동기 중심 |
| TypeBox | 약함 | 동기 검증 위주 |
10.5 OpenAPI 익스포트
| 라이브러리 | 지원 | 도구 |
|---|---|---|
| TypeBox | 매우 좋음 | Fastify + Type Provider |
| ArkType | 좋음 | .toJsonSchema() 직결 |
| Zod | 좋음 | @asteasolutions/zod-to-openapi, @hono/zod-openapi |
| Effect Schema | 좋음 | Effect HTTP·OpenAPI 어댑터 |
| Valibot | 좋음 | @valibot/to-json-schema |
10.6 런타임 비용 (대략, 핫 패스 기준)
| 라이브러리 | 검증 속도 | 비고 |
|---|---|---|
| TypeBox + AJV (compile) | 가장 빠름 | V8 친화 컴파일 |
| Valibot | 매우 빠름 | 함수 합성 단순 |
| ArkType | 빠름 | 컴파일된 검증자 |
| Zod 4 | 빠름 | 큰 폭 개선 |
| Effect Schema | 중간~빠름 | Effect 오버헤드 |
| Zod 3 | 보통 | 깊은 객체 약함 |
| Joi | 느림 | 무거운 옛 API |
(정확한 수치는 케이스마다 다름. 각 라이브러리 공식 벤치마크와 자신의 워크로드에서 측정 권장.)
10.7 생태계 (어댑터·통합)
| 라이브러리 | tRPC | RHF | OpenAI/Anthropic SDK | Fastify | Hono | Next.js |
|---|---|---|---|---|---|---|
| Zod | 일급 | 일급 | 일급 | 가능 | 가능 | 일급 |
| Valibot | 지원 | 일급 (@hookform/resolvers/valibot) | 외부 변환 | 가능 | 가능 | 가능 |
| ArkType | 지원 | 지원 | 외부 변환 | 가능 | 가능 | 가능 |
| TypeBox | 부분 | 부분 | 직접 | 일급 | 일급 (@hono/typebox-validator) | 가능 |
| Effect Schema | 부분 | 부분 | 외부 변환 | 가능 | 가능 | 가능 |
11장 · Yup·Joi·Superstruct — 살아 있지만
11.1 Yup
Formik 시대의 표준. 폼 검증에서 여전히 쓰이지만, 신규 채택은 줄어든다. Zod·Valibot에 비해 TS 추론이 약하고 번들이 무거움.
import * as yup from 'yup'
const User = yup.object({
email: yup.string().email().required(),
age: yup.number().integer().min(0).max(150).required(),
name: yup.string().min(1).required(),
})
11.2 Joi
Node 백엔드의 옛 표준. 브라우저 친화도 낮음(번들 큼, 의존성 깊음). 서버 검증에서 레거시 코드베이스에 많이 남아 있다.
const Joi = require('joi')
const User = Joi.object({
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150).required(),
name: Joi.string().min(1).required(),
})
11.3 Superstruct
작고 가벼운 검증. io-ts보다 친절하지만, Zod·Valibot에 비하면 에코시스템이 작다. 작은 프로젝트·라이브러리 내부 검증에는 여전히 좋은 선택.
import { object, string, number, refine } from 'superstruct'
const Email = refine(string(), 'email', (v) => /.+@.+/.test(v))
const User = object({
email: Email,
age: number(),
name: string(),
})
11.4 io-ts
전통 함수형 진영. Effect Schema가 그 자리를 빠르게 가져가고 있다. 새 코드에서 io-ts를 자발적으로 시작하는 경우는 줄어든다.
12장 · Standard Schema — 라이브러리 간 호환
2024년 말~2025년 사이 등장한 Standard Schema 스펙은 검증 라이브러리들이 합의한 최소 인터페이스다. tRPC·Hono·React Hook Form 같은 도구가 한 어댑터만으로 Zod·Valibot·ArkType을 받게 하려는 시도.
핵심 인터페이스(간단화).
interface StandardSchemaV1<Input = unknown, Output = Input> {
'~standard': {
version: 1
vendor: string
validate: (value: unknown) => StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>
types?: { input: Input; output: Output }
}
}
Zod·Valibot·ArkType·Effect Schema 모두 이 인터페이스를 구현한다. 결과 — 라이브러리 골라잡기 비용이 더 낮아졌다. 한 코드베이스에 여러 라이브러리가 공존하거나, 다른 도구로 갈아탈 때의 비용이 줄었다.
다만 Standard Schema는 최소 공통일 뿐 라이브러리 고유 기능(transform, refine, brand 등)은 어댑터 작성자가 해야 할 일이 남는다.
13장 · 무엇을 언제 골라야 하나 — 솔직한 결론
자, 정답은 항상 그렇듯 "상황 따라 다르다". 다만 패턴이 있다.
Zod 4를 고를 때
- 가장 큰 생태계가 필요. tRPC·Next.js·React Hook Form·OpenAI/Anthropic SDK 모두 일급.
- 학습 자료·튜토리얼·답변 풀(Stack Overflow)이 가장 두꺼움.
- 번들 한 KB의 차이가 중요하지 않은 백엔드·풀스택.
- 팀에 Zod 경험자가 이미 있음.
Valibot을 고를 때
- 프런트엔드·엣지 우선. 번들이 곧 비용.
- 함수형 합성을 선호.
- React Hook Form·React Router·Next.js 클라이언트 폼 검증.
- "Zod와 거의 같은 일을 더 작게" — 명확한 동기.
ArkType을 고를 때
- TS 추론 품질이 최상이어야 함.
- TS 표현을 그대로 스키마로 쓰는 사고가 자연스러움.
- JSON Schema 빌트인이 필요(외부 패키지 의존 회피).
- 자체 미니 DSL에 적응할 수 있는 팀.
TypeBox를 고를 때
- API 백엔드 — Fastify·Hono·Express + OpenAPI.
- AI 도구 호출 — JSON Schema를 직접 LLM에 보냄.
- 핫 패스 검증 — AJV로 가장 빠른 런타임.
- 스키마 = OpenAPI = 검증 = 직렬화. 한 정의 네 가지 일.
Effect Schema를 고를 때
- 팀이 이미 Effect 풀스택을 가는 중.
- 양방향 인코딩·변환이 핵심(Date·Brand·BigDecimal 등).
- 타입드 에러를 컴파일러로 강제하고 싶음.
- 함수형·카테고리 친화 팀.
Yup·Joi·Superstruct를 고를 때
- 레거시 코드베이스 유지보수.
- Yup — Formik 기반 코드.
- Joi — Hapi.js 백엔드.
- Superstruct — 의존성 최소 작은 라이브러리.
안티 체크리스트 (피해야 할 패턴)
- 한 코드베이스에 검증 라이브러리 3개 — Standard Schema가 있어도 학습·디버그 비용은 누적.
- "Valibot이 더 작아서 Zod에서 갈아탔다"는 결정을 측정 없이 — 큰 스키마에선 차이가 줄어든다.
- TypeBox로 모든 폼 검증 — 폼 라이브러리 어댑터가 빈약. Zod·Valibot이 자연스러움.
- AI 도구 호출에 Zod 그대로 보내기 — 변환을 거치되, 변환 후 JSON Schema를 한 번 점검.
z.refine/v.check로 잘 안 변환되는 검증을 잔뜩 — 변환 후 그 검증은 사라짐. 핵심 검증은 변환 가능한 형태로.
에필로그 — 스키마는 API 경계의 진실
검증 라이브러리 선택은 얼마나 작은가, 얼마나 친숙한가, 얼마나 멀리 갈 수 있는가의 트레이드오프다. 한쪽이 우월하지 않다. 다만 2026년 5월의 풍경은 분명하다.
- JSON Schema 호환이 1군 자격이 됐다 — AI 도구 호출이 강제했다. Zod도 외부 패키지로 따라잡았고, TypeBox·ArkType·Effect Schema는 빌트인.
- 번들 크기 경쟁이 끝나지 않았다 — Valibot이 정점을 보였고, Zod 4가 큰 폭으로 따라왔다. 차이는 작아지지만 여전히 의미 있는 차이.
- TS 추론은 표준 자격 — 어떤 도구도 추론이 약하면 신규 채택에서 떨어진다.
- Standard Schema로 갈아타기 비용이 낮아졌다 — 더 이상 "한 번 결정하면 평생"이 아님.
- 풀스택 한 스키마가 점점 보편 — 같은 스키마로 서버 검증·클라이언트 폼·문서·AI 도구 호출까지.
결정 체크리스트
- API 경계가 최우선인가? — TypeBox 또는 Zod +
zod-to-openapi. - 폼·번들·엣지가 최우선인가? — Valibot 또는 Zod 4-mini.
- TS 추론 품질이 최상이어야 하나? — ArkType.
- AI 도구 호출 JSON Schema가 중심인가? — TypeBox 또는 ArkType.
- Effect 풀스택 중인가? — Effect Schema.
- 학습·튜토리얼·생태계 두께가 중요? — Zod.
- 비동기 검증이 일상? — Zod·Valibot·Effect Schema.
- 양방향 변환·브랜드 타입이 필요? — Effect Schema 또는 Zod transform.
안티 패턴
- 측정 없이 라이브러리 갈아타기 — 결정은 측정 후.
- AI 도구에 변환 결과를 보지 않고 보내기 —
additionalProperties·required누락이 잦음. - JSON Schema로 표현 못 하는
refine을 핵심 검증으로 — 변환 시 사라짐. - 폼 검증에 TypeBox — RHF 어댑터가 약함. Zod·Valibot이 자연스러움.
- 큰 객체에 매 요청마다
z.parse반복 — 핫 패스라면 컴파일된 검증자(TypeBox + AJV) 고려. - 한 스키마 두 진실의 원천 — DB 스키마와 검증 스키마가 다른 곳에 정의되어 미스매치.
- 에러 메시지 사용자화 안 하기 — 라이브러리 기본 에러 메시지가 사용자에게 노출됨.
다음 글 예고
다음 글 후보: AI 도구 호출 JSON Schema 디버깅 — OpenAI structured outputs·Anthropic tool use 실수 모음, Zod 4 마이그레이션 후기 — 코드베이스 5만 줄 한 달 작업, Valibot 깊게 — 트리 셰이커가 실제로 어떻게 떨어내는가.
"검증은 코드의 경계다. 경계가 명확한 코드만큼 멀리 간다."
— TypeScript 데이터 검증 2026, 끝.
참고 / References
- Zod 공식
- Zod GitHub — colinhacks/zod
- Zod v4 릴리즈 노트
- Valibot 공식
- Valibot GitHub — fabian-hiller/valibot
@valibot/to-json-schema- ArkType 공식
- ArkType GitHub — arktypeio/arktype
- TypeBox 공식 README
- TypeBox + Fastify Type Provider
- Effect 공식
- Effect Schema 문서
- Standard Schema 공식
- Standard Schema GitHub
- AJV — JSON Schema 검증기
zod-to-json-schema@asteasolutions/zod-to-openapi@hono/zod-openapi@hookform/resolvers- OpenAI function calling 문서
- OpenAI structured outputs 발표
- Anthropic tool use 문서
- Yup 공식
- Joi 공식
- Superstruct 공식
- io-ts 공식
- tRPC 공식
- React Hook Form
TypeScript Data Validation 2026 — Zod·Valibot·ArkType·TypeBox·Effect Schema Deep Comparison (Runtime Validation in the AI Tool-Calling Era)
Prologue — The era when every API boundary demands a schema
In 2022, "what should we use for TypeScript runtime validation?" had one answer: Zod. Before that, Joi, Yup, or hand-written type guards.
In 2026 the same question has five candidates. A lot happened in between.
- tRPC became standard. Putting a Zod schema on every procedure input became the default idiom of full-stack TS. Schema = API boundary.
- React Hook Form + zodResolver became the de-facto form pattern. Form validation = the same schema.
- AI tool calling (function calling, tool use) mandates JSON Schema. OpenAI, Anthropic, and Gemini all accept tool definitions as JSON Schema. If your TS schema can't export JSON Schema, you're in trouble.
- OpenAPI auto-generation is hot again. Hono, tRPC-openapi,
@asteasolutions/zod-to-openapi, TypeBox's built-in interop. One schema, three artifacts: client, server, docs. - Bundle size = money. More code runs at the edge — Cloudflare Workers, Vercel Edge. The 1MB-Zod era is gone, but a camp now counts every KB.
Five camps emerged.
- Zod 4 — The de-facto standard. Version 4 hit the two old weaknesses directly: tree-shaking and parser speed.
- Valibot — Functional, modular API. Only what you use ends up in the bundle. Wins frontend forms and edge.
- ArkType — Uses TS strings as schema.
type({ email: 'string.email' }). 100% TS-inference alignment. - TypeBox — Schemas you write are JSON Schema. Direct line to Fastify, OpenAPI, AI tool calling.
- Effect Schema — Effect ecosystem's validator. Typed errors, composable transforms, bidirectional encoding.
- Yup / Joi / Superstruct — Alive but new adoption keeps shrinking.
This post compares these libraries head-to-head as of May 2026. Seven axes: bundle, inference, JSON Schema, async, OpenAPI, runtime cost, ecosystem. Plus the same User schema in all five.
1. Landscape — Map of data validation libraries
Classification first. Not everything sits in the same place.
| Category | Representative | One-line summary |
|---|---|---|
| Object builder (chaining) | Zod, Yup, Joi | z.string().email() style. Most familiar. |
| Modular functions | Valibot | string([email()]). Tree-shake friendly. |
| TS syntax as schema | ArkType | Use TS type expressions at runtime. |
| JSON Schema native | TypeBox | What you write is JSON Schema. |
| Functional + Effect | Effect Schema | Typed errors, bidirectional encoding. |
| Lightweight assertion | Superstruct, io-ts | Small, composable, slightly older style. |
This post focuses on the five bold splits — Zod, Valibot, ArkType, TypeBox, Effect Schema. Yup, Joi, and Superstruct get a short chapter near the end.
Why these five
- Zod 4 — Default for new TypeScript projects. Appears in tRPC, Next.js, React Hook Form everywhere. Zod 4 announced in 2024 and stabilized in 2025–2026 erased the tree-shaking weakness.
- Valibot — Took off in 2024. Function-level imports let unused code drop out of the bundle. Rapidly gained share in frontend and edge.
- ArkType — A worldview shift: accept TS expressions directly. New projects after v2 stabilization.
- TypeBox — Recommended by Fastify; paired with AJV it's one of the fastest runtimes. First pick when LLMs need raw JSON Schema for tool calling.
- Effect Schema — The validator if your team goes all-in on Effect. Hard to justify otherwise.
2. Seven-axis comparison matrix
Before the deep dives, here's the big picture.
| Axis | Zod 4 | Valibot | ArkType | TypeBox | Effect Schema |
|---|---|---|---|---|---|
| Bundle (core) | Medium | Very small (modular) | Small | Very small | Large (Effect core included) |
| TS inference quality | Very good | Very good | Best (TS expressions as-is) | Good | Very good |
| JSON Schema export | External pkg | External pkg | Built-in | Built-in (definition IS JSON Schema) | Built-in |
| Async validation | Yes | Yes | Partial | No (sync-focused) | Yes |
| OpenAPI export | zod-to-openapi | @valibot/to-json-schema | Built-in | Built-in (Fastify/AJV) | @effect/schema-openapi |
| Runtime cost | Fast (large gain in v4) | Very fast | Fast | Fastest (when AJV-compiled) | Medium–fast |
| Ecosystem | Largest | Growing fast | Medium | API/Fastify camp | Effect users |
Don't decide from this table alone. Next chapters pin down what each does and doesn't do.
3. Zod — Why it became the standard, and Zod 4's comeback
Since landing in 2020, Zod has been the de-facto TypeScript validation standard. Three reasons.
- Chaining API familiarity.
z.string().email().min(5)reads as intent. Near-zero learning cost for anyone coming from Joi or Yup. z.infertype inference. Derive TS types directly from the schema. Schema = single source of truth for the type.- Ecosystem explosion. tRPC,
@hookform/resolvers, OpenAI SDK, LangChain, Next.js Server Actions — all accept Zod first-class.
Zod 3-era weaknesses
Two criticisms followed Zod for years.
- Bundle size. Because the chaining API pulls in the whole index together, tree-shaking was weak. Even a bare
z.string()dragged in tens of KB. - Runtime cost. Parsing deeply nested objects was surprisingly heavy. Hot paths (thousands of calls per request) felt it.
What Zod 4 changed
Zod 4 beta arrived October 2024; GA followed in 2025. Three big changes.
- Tree-shakeable build. Unused validators drop out of the bundle. You still write
import { z } from 'zod', but the resulting bundle is smaller. - Parser rewrite. Simplified internals and optimized hot paths. Official benchmarks show meaningful gains over v3 (consult release notes for numbers).
- Split builds. A core
zodandzod/v4, plus a mini build (zod/v4-mini) targeting Valibot-class bundles.
Same User schema in Zod
import { z } from 'zod'
const User = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
name: z.string().min(1),
})
type User = z.infer<typeof User>
const parsed = User.parse({ email: 'foo@example.com', age: 30, name: 'Foo' })
// ^ parsed: User
safeParse for errors as values:
const result = User.safeParse(unknown)
if (!result.success) {
// result.error is a ZodError; pretty issues at result.error.issues
console.error(result.error.flatten())
}
Zod strengths (2026)
- Largest ecosystem. New libraries and SDKs almost auto-ship a Zod adapter.
- Bundle and speed weaknesses largely closed in v4.
- Async validation (
z.string().refine(async ...)) is smooth. - Learning material dominates.
Zod weaknesses (2026)
- Absolute bundle still bigger than Valibot. Not a candidate if you fight for KB on edge workers.
- Some APIs like
z.discriminatedUnioncarry a slight learning cost. - JSON Schema export needs an external package (
@asteasolutions/zod-to-openapietc.). Native support is partial.
4. Valibot — Shrinking the bundle with functional modules
Valibot exploded between late 2023 and 2024. Core philosophy:
"Write the basics as functions. What you don't use isn't in the bundle."
Where Zod is z.string().email().min(5), Valibot is function composition.
import * as v from 'valibot'
const User = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
name: v.pipe(v.string(), v.minLength(1)),
})
type User = v.InferOutput<typeof User>
const parsed = v.parse(User, { email: 'foo@example.com', age: 30, name: 'Foo' })
Compose validators with v.pipe(...), then apply with v.parse/v.safeParse. Functions are all named exports — esbuild/Rollup drops unused ones.
Bundle gap
The picture from the official site and community benchmarks:
- Using only
v.string()once in Valibot pulls in a few hundred bytes. - The same code in Zod 3 pulled in tens of KB. Zod 4 closes that gap a lot, but Valibot is still smaller.
- With a big schema (many validators), the gap narrows. The smaller you start, the more Valibot wins.
Valibot's BaseSchema API
Stable since Valibot 1.0 GA in 2024. Main functions:
v.object,v.array,v.tuple,v.union,v.intersect,v.recordv.string,v.number,v.boolean,v.literal,v.date,v.bigintv.pipe(schema, ...actions)— validator compositionv.transform(fn),v.brand(name)— transforms and brand types
Async uses v.parseAsync/v.safeParseAsync.
Valibot strengths
- Bundle leader — smallest in frontend and edge.
- Inference quality — both
InferOutputandInferInputare precise. - Functional means easy composition — low cost to write custom validators.
- Fast-growing ecosystem — Standard Schema support,
@valibot/to-json-schema,@hookform/resolvers/valibot.
Valibot weaknesses
- API learning curve — initially awkward if you're used to chaining.
- Custom error messages take slightly more work.
- Some libraries that ship a Zod adapter still lack a Valibot one or add it late.
- OpenAPI export is thinner than Zod's — more hand-writing required.
5. ArkType — TypeScript syntax, executable at runtime
ArkType drew attention with its v2 GA in 2024. Different premise:
"Take a TypeScript expression as a string and turn it into a runtime schema."
Same User schema:
import { type } from 'arktype'
const User = type({
email: 'string.email',
age: 'number.integer >= 0',
name: 'string > 0',
})
// type inference: TS understands the expression precisely
type User = typeof User.infer
const out = User({ email: 'foo@example.com', age: 30, name: 'Foo' })
if (out instanceof type.errors) {
console.error(out.summary)
} else {
// out is the validated value
}
The trick: an expression like 'number.integer >= 0' is parsed by TS template literal types into a precise type. ArkType defines a mini DSL, parses it at the type level, and produces a schema whose inferred type matches a hand-written TS type 1:1.
What ArkType does differently
- TS expressions directly —
'string | number','(string | number)[]>=2'feel native. - Type-first —
typeof User.inferis instant; no extra.infercall cost (it's a type-level operation). - Built-in JSON Schema export —
User.toJsonSchema()is one line. - Pre-compiled validators — internally builds a validation function at schema definition time.
Object form
You can also pass a TS object directly:
import { type } from 'arktype'
const Post = type({
id: 'number.integer',
title: 'string > 0',
tags: 'string[]',
author: {
id: 'number',
name: 'string',
},
publishedAt: 'Date',
})
ArkType strengths
- Best-in-class TS affinity — near-zero learning cost if you know TS well.
- Built-in JSON Schema — no external package.
- Fast runtime — pre-compiled validators.
- Calling the schema is the validator —
User(value), intuitive.
ArkType weaknesses
- Ecosystem still smaller — tRPC and React Hook Form adapters exist but aren't as thick as Zod's.
- Less material for error-message localization and customization.
- Async validation is partial — sync-focused design.
- The DSL strings take getting used to —
'number.integer >= 0'feels like magic at first.
6. TypeBox — What you write is JSON Schema
TypeBox emerged in 2021, was loved by the Fastify camp, and got a second wind in the AI tool-calling era.
"My schema is literally JSON Schema. Compile with AJV and you've got the fastest runtime."
Same User schema:
import { Type, Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'
const User = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
name: Type.String({ minLength: 1 }),
})
type User = Static<typeof User>
const ok = Value.Check(User, unknown)
const errors = [...Value.Errors(User, unknown)]
What Type.Object({ ... }) returns IS a JSON Schema document. JSON.stringify(User) gives a standard JSON Schema.
Pairing with AJV — fastest runtime
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const ajv = addFormats(new Ajv())
const check = ajv.compile(User)
const ok = check(value) // compiled function — very fast
AJV compiles JSON Schema into V8-friendly code. The result is generally the fastest runtime validator in this category.
TypeBox + Fastify
import Fastify from 'fastify'
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>()
app.post('/users', {
schema: { body: User, response: { 200: User } },
}, async (req) => {
// req.body is typed as User automatically
return req.body
})
The schema doubles as OpenAPI response spec, runtime validator, and response serializer. One definition, four jobs.
TypeBox for AI tool calling
OpenAI, Anthropic, and Gemini function/tool definitions are JSON Schema.
const GetWeather = Type.Object({
city: Type.String(),
units: Type.Optional(Type.Union([Type.Literal('c'), Type.Literal('f')])),
})
const tool = {
type: 'function',
function: {
name: 'get_weather',
description: 'Current weather for a given city',
parameters: GetWeather, // already JSON Schema
},
}
No conversion via zod-to-json-schema. Nothing lost in translation (format, description, etc.).
TypeBox strengths
- JSON Schema as-is — direct line to OpenAPI, AI tool calling, AJV.
- Fastest runtime with AJV — optimal for hot-path validation.
- Plenty of middleware for Fastify, Hono, Express.
- Very small bundle —
Type.*itself is light.
TypeBox weaknesses
- Slightly verbose API —
Type.Object({ x: Type.String() })feels long. - No chaining flavor — you'll miss
z.string().email().min(5). - Ecosystem leans toward Fastify — fewer React form adapters than Zod/Valibot.
- Async is weak — JSON Schema is sync-centric.
7. Effect Schema — The Effect ecosystem's validator
Effect is a Scala/Haskell-influenced full-stack TS functional framework. Effect Schema is its data validation/transform module.
"Validation is bidirectional transformation. And every error belongs in the type."
Same User schema:
import { Schema } from 'effect'
const User = Schema.Struct({
email: Schema.String.pipe(Schema.email()),
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
name: Schema.NonEmptyString,
})
type User = Schema.Schema.Type<typeof User>
const decode = Schema.decodeUnknownSync(User)
const out = decode({ email: 'foo@example.com', age: 30, name: 'Foo' })
decodeUnknownSync is the sync entry. decodeUnknown/decode returns an Effect value — composable inside the Effect stack.
What Effect Schema does differently
- Bidirectional
decode↔encode— JSON to domain, domain to JSON. Date, BigDecimal, Brand types are natural. - Typed errors —
ParseErrorlives in the type, so missing error handling is a compile error. - Composition —
Schema.compose,Schema.transform,Schema.filter. - Built-in JSON Schema —
JSONSchema.make(User)in one line.
Bidirectional transform example
import { Schema } from 'effect'
// Convert ISO string -> domain Date both ways
const DateFromString = Schema.transform(Schema.String, Schema.Date, {
decode: (s) => new Date(s),
encode: (d) => d.toISOString(),
})
const Event = Schema.Struct({
name: Schema.NonEmptyString,
at: DateFromString,
})
const decoded = Schema.decodeUnknownSync(Event)({ name: 'Launch', at: '2026-05-14T00:00:00Z' })
// ^ decoded.at is a real Date
Effect Schema strengths
- Elegant bidirectional validation/transform — what other libraries make you hand-write naturally falls out.
- Typed errors block missing error handling.
- Composable with Effect's full stack — HTTP, DB, cache, retry as one model.
- Built-in JSON Schema.
Effect Schema weaknesses
- Heavy learning and bundle cost if you don't use Effect — Effect core comes along.
- Reads longer than chaining —
.pipe(Schema.email())feels awkward until it clicks. - Ecosystem — form and tRPC adapters are partial.
- Learning curve — you must learn Effect. Category-theory and functional fluency helps.
8. Same schema, five libraries — User(email + age + name)
Five side by side. Same semantics.
8.1 Zod
import { z } from 'zod'
const User = z.object({
email: z.string().email(),
age: z.number().int().min(0).max(150),
name: z.string().min(1),
})
type User = z.infer<typeof User>
8.2 Valibot
import * as v from 'valibot'
const User = v.object({
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
name: v.pipe(v.string(), v.minLength(1)),
})
type User = v.InferOutput<typeof User>
8.3 ArkType
import { type } from 'arktype'
const User = type({
email: 'string.email',
age: '0 <= number.integer <= 150',
name: 'string > 0',
})
type User = typeof User.infer
8.4 TypeBox
import { Type, Static } from '@sinclair/typebox'
const User = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Integer({ minimum: 0, maximum: 150 }),
name: Type.String({ minLength: 1 }),
})
type User = Static<typeof User>
8.5 Effect Schema
import { Schema } from 'effect'
const User = Schema.Struct({
email: Schema.String.pipe(Schema.email()),
age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
name: Schema.NonEmptyString,
})
type User = Schema.Schema.Type<typeof User>
Comparison — same schema, different philosophies
| Library | Lines | Style | Inference call |
|---|---|---|---|
| Zod | Short | Chaining | z.infer |
| Valibot | Medium | Function composition (pipe) | v.InferOutput |
| ArkType | Shortest | TS DSL string | typeof X.infer |
| TypeBox | Medium | Type builder | Static |
| Effect Schema | Medium | .pipe chaining | Schema.Schema.Type |
Zod and ArkType win readability. Valibot and TypeBox win bundle and tree-shaking. Effect Schema wins when you need transforms and typed errors.
9. The AI tool-calling era — JSON Schema is mandatory
The biggest trend in 2024–2025: every major LLM accepts tool definitions as JSON Schema.
9.1 OpenAI function calling
const tools = [{
type: 'function',
function: {
name: 'get_weather',
description: 'Get the current weather in a given city',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['c', 'f'] },
},
required: ['city'],
},
},
}]
parameters is standard JSON Schema. Either write it raw or convert from a TS schema library.
9.2 Anthropic tool use
const tools = [{
name: 'get_weather',
description: 'Get the current weather in a given city',
input_schema: {
type: 'object',
properties: {
city: { type: 'string' },
units: { type: 'string', enum: ['c', 'f'] },
},
required: ['city'],
},
}]
Only the key name differs (input_schema). Shape is identical.
9.3 JSON Schema conversion per library
- Zod —
@asteasolutions/zod-to-openapi,zod-to-json-schema, and the OpenAI SDK accepts Zod directly viazodFunction. Anthropic SDK is adding direct Zod support too. - Valibot —
@valibot/to-json-schema(official). - ArkType —
User.toJsonSchema()built-in. - TypeBox — no conversion needed. The definition is already JSON Schema.
- Effect Schema —
JSONSchema.make(User)built-in.
9.4 What gets lost in conversion
JSON Schema is narrower than TS expressions. Conversion frequently drops:
- Bidirectional transforms like
z.transform/v.transform— JSON Schema only describes input validation. - Brand types from
z.brand— flatten to a plainstringetc. - Custom
refinefunctions — anything not expressible in JSON Schema disappears. - Recursive schemas —
$refsupport is uneven across libraries and LLMs. Flatten or cap recursion depth for safety.
9.5 OpenAI structured outputs and strict mode
The structured outputs feature OpenAI launched in late 2024 enforces a JSON Schema subset (e.g., additionalProperties: false, required fields, no $ref, etc.). If your TS library's emitted JSON Schema breaks those rules, OpenAI rejects it.
Practical tips:
- Keep tool schemas as simple as possible (flat,
additionalProperties: false). - Use
descriptionaggressively — the best hint for the LLM. - Avoid recursion and
$ref; flatten LLM-friendly. - Sanity-check the converted output — print the resulting JSON Schema and verify it matches intent.
10. Seven-axis deep comparison
10.1 Bundle size
Roughly, with a small schema usage profile.
| Library | Small usage | Large usage | Notes |
|---|---|---|---|
| Valibot | Smallest (hundreds of B) | Small | Pinnacle of tree-shaking |
| TypeBox | Very small | Small | Type.* is itself light |
| ArkType | Small | Medium | Parser ships with it |
| Zod 4 | Medium (big drop) | Medium | v4-mini even smaller |
| Zod 3 | Large | Large | Weak tree-shaking |
| Effect Schema | Large | Large | Includes Effect core |
| Yup | Medium | Medium | |
| Joi | Large | Large | Low browser affinity |
10.2 TS inference quality
| Library | Inference | Notes |
|---|---|---|
| ArkType | Best | TS expressions as-is |
| Zod 4 | Very good | z.infer everywhere |
| Valibot | Very good | InferOutput/InferInput |
| Effect Schema | Very good | Includes bidirectional types |
| TypeBox | Good | Static |
| Yup | Fair | Inference is weak |
| Joi | Weak | Hand-write types |
10.3 JSON Schema export
| Library | Support | How |
|---|---|---|
| TypeBox | Native | Definition itself |
| ArkType | Built-in | .toJsonSchema() |
| Effect Schema | Built-in | JSONSchema.make() |
| Valibot | Official external | @valibot/to-json-schema |
| Zod | External | zod-to-json-schema, @asteasolutions/zod-to-openapi |
| Yup | Thin | Unofficial converters |
| Joi | None | Write by hand |
10.4 Async validation
| Library | Support | Pattern |
|---|---|---|
| Zod | Strong | z.string().refine(async ...) + parseAsync |
| Valibot | Strong | parseAsync/safeParseAsync |
| Effect Schema | Strong | Effect is async by nature |
| ArkType | Partial | Sync-focused |
| TypeBox | Weak | Sync-only design |
10.5 OpenAPI export
| Library | Support | Tooling |
|---|---|---|
| TypeBox | Very good | Fastify + Type Provider |
| ArkType | Good | .toJsonSchema() direct |
| Zod | Good | @asteasolutions/zod-to-openapi, @hono/zod-openapi |
| Effect Schema | Good | Effect HTTP + OpenAPI adapters |
| Valibot | Good | @valibot/to-json-schema |
10.6 Runtime cost (rough, hot-path)
| Library | Validation speed | Notes |
|---|---|---|
| TypeBox + AJV (compile) | Fastest | V8-friendly compiled code |
| Valibot | Very fast | Simple function composition |
| ArkType | Fast | Pre-compiled validators |
| Zod 4 | Fast | Big improvement |
| Effect Schema | Medium–fast | Effect overhead |
| Zod 3 | Medium | Weak on deep objects |
| Joi | Slow | Heavy older API |
(Numbers depend on the case. Use each library's official benchmarks and measure on your own workload.)
10.7 Ecosystem (adapters and integrations)
| Library | tRPC | RHF | OpenAI/Anthropic SDK | Fastify | Hono | Next.js |
|---|---|---|---|---|---|---|
| Zod | First-class | First-class | First-class | OK | OK | First-class |
| Valibot | Supported | First-class (@hookform/resolvers/valibot) | External convert | OK | OK | OK |
| ArkType | Supported | Supported | External convert | OK | OK | OK |
| TypeBox | Partial | Partial | Direct | First-class | First-class (@hono/typebox-validator) | OK |
| Effect Schema | Partial | Partial | External convert | OK | OK | OK |
11. Yup, Joi, Superstruct — Alive but…
11.1 Yup
Standard from the Formik era. Still common in form validation but new adoption is shrinking. Weaker TS inference and heavier bundle than Zod/Valibot.
import * as yup from 'yup'
const User = yup.object({
email: yup.string().email().required(),
age: yup.number().integer().min(0).max(150).required(),
name: yup.string().min(1).required(),
})
11.2 Joi
The old Node backend standard. Low browser affinity (large bundle, deep deps). Still very present in legacy server codebases.
const Joi = require('joi')
const User = Joi.object({
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150).required(),
name: Joi.string().min(1).required(),
})
11.3 Superstruct
Small, lightweight validation. Friendlier than io-ts but a smaller ecosystem than Zod/Valibot. A solid pick for small projects and internal library validation.
import { object, string, number, refine } from 'superstruct'
const Email = refine(string(), 'email', (v) => /.+@.+/.test(v))
const User = object({
email: Email,
age: number(),
name: string(),
})
11.4 io-ts
The traditional functional camp. Effect Schema is rapidly taking that seat. Few new projects start with io-ts voluntarily.
12. Standard Schema — Cross-library compatibility
Around late 2024 to 2025 a Standard Schema spec emerged — the minimum interface validation libraries agreed on. The point: let tools like tRPC, Hono, and React Hook Form accept Zod/Valibot/ArkType through a single adapter.
Core interface (simplified):
interface StandardSchemaV1<Input = unknown, Output = Input> {
'~standard': {
version: 1
vendor: string
validate: (value: unknown) => StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>
types?: { input: Input; output: Output }
}
}
Zod, Valibot, ArkType, and Effect Schema all implement this. The upshot — library-switching cost dropped. Coexisting libraries in one codebase, or migrating between them, is cheaper than before.
That said, Standard Schema is the minimum common denominator. Library-specific features (transform, refine, brand…) still need explicit adapter work.
13. What to pick when — An honest verdict
The honest answer is, as always, "it depends." But patterns exist.
Pick Zod 4 when
- You need the largest ecosystem. tRPC, Next.js, React Hook Form, OpenAI/Anthropic SDKs all first-class.
- The thickest pile of tutorials, docs, and Stack Overflow answers matters.
- KB-level bundle differences don't bite (backend or full-stack).
- The team already has Zod experience.
Pick Valibot when
- Frontend or edge is the priority. Bundle is the cost.
- You prefer functional composition.
- React Hook Form, React Router, Next.js client-side form validation.
- "Same job as Zod, smaller" is a clear motivation.
Pick ArkType when
- You need best-in-class TS inference.
- The "TS expression as schema" worldview clicks.
- Built-in JSON Schema export (avoid external deps) is a must.
- The team can adapt to a mini DSL.
Pick TypeBox when
- Building API backends — Fastify, Hono, Express + OpenAPI.
- AI tool calling — feed JSON Schema directly to LLMs.
- Hot-path validation — AJV-compiled is the fastest you'll get.
- Schema = OpenAPI = validator = serializer. One def, four jobs.
Pick Effect Schema when
- The team is going all-in on Effect.
- Bidirectional encoding and transforms are core (Date, Brand, BigDecimal).
- You want the compiler to enforce error handling via typed errors.
- A functional / category-theory friendly team.
Pick Yup, Joi, Superstruct when
- Maintaining legacy.
- Yup — Formik-based code.
- Joi — Hapi.js backends.
- Superstruct — dependency-minimal small libraries.
Anti-checklist (patterns to avoid)
- Three validation libraries in one codebase — even with Standard Schema, learning and debug cost piles up.
- "Valibot is smaller, so we switched from Zod" decided without measurement — large schemas narrow the gap.
- TypeBox for all form validation — form adapter ecosystem is thinner. Zod/Valibot fit naturally.
- Sending Zod straight to AI tool calls — convert and inspect the resulting JSON Schema.
- Stuffing
z.refine/v.checkwith logic that won't convert cleanly — that validation disappears after conversion. Keep core checks convertible.
Epilogue — Schemas are the truth at the API boundary
Picking a validation library is a tradeoff between how small, how familiar, how far you can take it. No single winner. But the May 2026 landscape is clear.
- JSON Schema interop is now a Tier-1 requirement — AI tool calling forced it. Zod caught up with external packages; TypeBox/ArkType/Effect Schema are native.
- The bundle race isn't over — Valibot set the bar; Zod 4 closed a lot of ground. The gap narrows, but it's still meaningful.
- TS inference is table stakes — weak inference kills a library's chance of new adoption.
- Standard Schema lowered switching cost — "pick once for life" is over.
- One schema for the whole stack is increasingly the norm — the same schema validates server, hydrates client forms, generates docs, and defines AI tool calls.
Decision checklist
- API boundary the top priority? — TypeBox, or Zod +
zod-to-openapi. - Forms / bundle / edge the top priority? — Valibot or Zod 4-mini.
- Need best TS inference? — ArkType.
- Centered on AI tool-calling JSON Schema? — TypeBox or ArkType.
- All-in on Effect? — Effect Schema.
- Care about ecosystem depth and learning resources? — Zod.
- Async validation an everyday thing? — Zod, Valibot, Effect Schema.
- Need bidirectional transforms or brand types? — Effect Schema or Zod transforms.
Anti-patterns
- Switching libraries without measuring.
- Sending the conversion result to AI tools without inspecting it — missing
additionalProperties/requiredhappens often. - Putting core validation in
refinethat JSON Schema can't represent — disappears in conversion. - TypeBox for forms — RHF adapter is weak. Zod/Valibot are natural.
- Repeating
z.parseon the same large object per request — for hot paths consider compiled validators (TypeBox + AJV). - Two sources of truth for one schema — DB shape and validation schema defined in different places and drifting.
- Skipping error-message customization — library default messages leak to users.
Coming up next
Candidates: Debugging AI tool-calling JSON Schema — common OpenAI structured outputs and Anthropic tool use mistakes, One month migrating a 50k-line codebase to Zod 4, Valibot deep dive — what the tree-shaker actually drops.
"Validation is the boundary of code. Code with clean boundaries travels far."
— TypeScript Data Validation 2026, end.
References
- Zod official
- Zod GitHub — colinhacks/zod
- Zod v4 release notes
- Valibot official
- Valibot GitHub — fabian-hiller/valibot
@valibot/to-json-schema- ArkType official
- ArkType GitHub — arktypeio/arktype
- TypeBox README
- TypeBox + Fastify Type Provider
- Effect official
- Effect Schema docs
- Standard Schema official
- Standard Schema GitHub
- AJV — JSON Schema validator
zod-to-json-schema@asteasolutions/zod-to-openapi@hono/zod-openapi@hookform/resolvers- OpenAI function calling docs
- OpenAI structured outputs announcement
- Anthropic tool use docs
- Yup official
- Joi official
- Superstruct official
- io-ts official
- tRPC official
- React Hook Form