ArkType, une alternative plus rapide à Zod
Comparez la syntaxe d’ArkType et de Zod, l’inférence des types, la vitesse de validation et le traitement des réponses API. Sachez quand choisir ArkType ou garder Zod.
ArkType est un validateur TypeScript à l’exécution : il exprime les schémas sous forme de chaînes à la syntaxe proche de TypeScript, puis les compile en validateurs optimisés. Pour une équipe disposant d’une base de code Zod stable, une migration complète en vaut rarement la peine. Pour un nouveau projet ou un chemin critique de validation, en revanche, l’essai se justifie.
Si vous utilisez Zod, vous avez sans doute déjà vu le graphique de benchmarks d’ArkType et vous êtes demandé si ce gain de vitesse valait l’apprentissage d’une nouvelle syntaxe.
Cet article fait migrer un unique schéma User de Zod vers ArkType. Il aborde l’inférence de types, les performances, la validation d’une réponse d’API et les compromis à envisager, puis indique clairement dans quels cas rester sur Zod. Les exemples utilisent ArkType 2.2 et Zod 4.
Points clés
- ArkType définit les schémas sous forme de chaînes proches de TypeScript :
"'android' | 'ios'"se lit exactement comme le type union qu’il produit, là où Zod écritz.enum(["android", "ios"]). - Appeler un type ArkType sur des données inconnues renvoie soit la valeur validée, soit une instance d’
ArkErrors; la vérification idiomatique est doncout instanceof type.errors. - La page d’accueil d’ArkType affirme que la bibliothèque est 20 fois plus rapide que Zod 4 à l’exécution. Ce chiffre provient de l’éditeur lui-même, et Zod 4.5 a depuis introduit
z.compile()pour les chemins critiques. - ArkType 2.2 accepte n’importe quel validateur Standard Schema dans
type(): un schéma Zod 4 existant peut donc être imbriqué dans une définition ArkType au lieu d’être réécrit au préalable.
Quelle est la différence essentielle entre ArkType et Zod ?
Zod construit un schéma à partir d’appels de méthodes chaînés, tandis qu’ArkType l’écrit sous forme de chaînes proches de TypeScript, si bien qu’il se lit comme le type qu’il produit. Un champ déclaré "(number | string)[]" correspond exactement à l’annotation TypeScript que vous auriez écrite de toute façon, simplement placée entre guillemets. Le guide « Your First Type » d’ArkType souligne que votre éditeur vérifie ces définitions en chaînes au fil de la saisie, avec autocomplétion, en s’appuyant sur le système de types de TypeScript lui-même.
Le même schéma User avec Zod et ArkType
Voici le même schéma à trois champs dans les deux bibliothèques : une chaîne obligatoire, une union à deux valeurs et un tableau facultatif de nombres ou de chaînes.
Zod 4 :
import * as z from "zod"
const User = z.object({
name: z.string(),
platform: z.enum(["android", "ios"]),
versions: z.array(z.union([z.number(), z.string()])).optional(),
})
ArkType 2.2 :
import { type } from "arktype"
const User = type({
name: "string",
platform: "'android' | 'ios'",
"versions?": "(number | string)[]",
})
La version ArkType ne comporte aucun appel de builder imbriqué. Le caractère facultatif change également de place : ArkType marque la clé avec ?, comme TypeScript, tandis que Zod appelle .optional() sur la valeur. ArkType prend aussi en charge une configuration globale exactOptionalPropertyTypes (ajoutée dans la version 2.1.12), qui reproduit l’option du compilateur TypeScript du même nom.
| Critère | Zod 4 | ArkType 2.2 |
|---|---|---|
| Syntaxe des schémas | Méthodes de builder chaînées | Chaînes et littéraux d’objet proches de TypeScript |
| Champ facultatif | .optional() sur la valeur | "key?" sur la clé |
| Type statique | z.infer<typeof User> | typeof User.infer |
| Validation de données inconnues | User.safeParse(data) | User(data) |
| Détection d’un échec | !result.success | out instanceof type.errors |
| Message d’erreur lisible | Construit à partir de result.error.issues | out.summary |
| Compilation | Optionnelle via z.compile() (Zod 4.5+) | Intégrée au traitement des définitions |
Inférence de types : typeof User.infer vs z.infer
Les deux bibliothèques dérivent le type statique à partir du schéma d’exécution : vous n’avez donc jamais à écrire l’interface à la main. Seule la syntaxe d’extraction diffère.
// Zod
type User = z.infer<typeof User>
// ArkType
type User = typeof User.infer
Dans Zod, z.infer est un utilitaire générique que l’on applique au type du schéma. Dans ArkType, infer est une propriété du type lui-même, accessible via typeof. Avec le schéma ci-dessus, les deux produisent la même structure : { name: string; platform: "android" | "ios"; versions?: (number | string)[] }.
ArkType est-il plus rapide que Zod ?
ArkType génère un validateur optimisé en amont, au moment de la création de chaque Type. La documentation de configuration d’ArkType décrit cette étape de précompilation ainsi qu’une option jitless qui permet de la désactiver. La page d’accueil d’ArkType affirme qu’ArkType est 20 fois plus rapide que Zod 4 à l’exécution. Il s’agit du benchmark de l’éditeur lui-même, et non d’une mesure indépendante.
Zod 4.5 a depuis réduit une partie de cet écart avec z.compile(). Comme l’explique la documentation de Zod consacrée à la compilation, z.compile() parcourt un schéma une seule fois et génère une fonction de vérification linéaire, que Zod exécute via new Function(). Si une entrée échoue à cette vérification rapide, Zod la transmet au parseur classique, de sorte que les erreurs détaillées restent identiques. Le README de Zod fait état d’une accélération médiane de 2,4x sur un benchmark de 55 schémas. Une comparaison avec Zod 4 non compilé ne vous dit donc rien des performances d’ArkType face à un schéma Zod compilé.
Pour la validation de formulaires, l’écart de vitesse entre ArkType et Zod a rarement de l’importance : quelques validations par interaction utilisateur ne constitueront pas votre goulot d’étranglement. La vitesse devient un critère lorsqu’un même schéma s’exécute des milliers de fois par seconde, par exemple dans des gestionnaires de requêtes ou des imports par lots.
Valider une réponse d’API de bout en bout
Le cas d’usage réel le plus courant consiste à vérifier une réponse fetch avant que le reste de l’application ne s’y fie. Voici la même fonction dans les deux bibliothèques.
ArkType :
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`)
const out = User(await res.json())
if (out instanceof type.errors) {
console.error(out.summary)
return null
}
return out // typed as User
}
Zod :
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`)
const result = User.safeParse(await res.json())
if (!result.success) {
console.error(result.error.issues)
return null
}
return result.data
}
Zod et ArkType ne renvoient pas la même structure à l’issue de la validation. La méthode safeParse de Zod renvoie un objet de résultat discriminé comportant success, data et error. Un type ArkType renvoie soit directement la valeur validée, soit une instance d’ArkErrors. Après la vérification instanceof, TypeScript affine le type de out en User. out.summary fournit un message unique et lisible qui recense chaque chemin en échec, la valeur attendue et la valeur reçue.
ArkErrors peut également être transmis directement à JSON.stringify(), une évolution apparue dans ArkType 2.1.10 et mentionnée dans les notes de version 2.2. Un échec de validation peut ainsi être intégré tel quel à une réponse d’erreur d’API ou à une entrée de log, sans écrire de formateur au préalable.
Le compromis : écosystème et familiarité contre nouvelle grammaire
Le principal avantage de Zod sur ArkType tient à tout ce qui l’entoure. Il dispose d’un vaste écosystème d’intégrations, et la plupart des développeurs TypeScript savent déjà lire un schéma Zod. Cet écart se réduit en partie grâce à Standard Schema, une interface commune qu’implémentent Zod, ArkType et Valibot. Avant de changer de bibliothèque, vérifiez que votre bibliothèque de formulaires, votre routeur et votre couche RPC acceptent Standard Schema.
Les coûts d’ArkType sont bien réels :
- Une nouvelle grammaire. Les unions, tableaux et clés facultatives semblent familiers, mais les contraintes et les expressions plus avancées forment un langage de chaînes que votre équipe devra apprendre.
- Les erreurs apparaissent comme des erreurs de type sur des chaînes. Une faute de frappe comme
"strng"est détectée par TypeScript dans l’éditeur, mais sous la forme d’une erreur sur une définition en chaîne plutôt que d’une méthode inexistante : votre équipe devra s’habituer à lire un nouveau type de message d’erreur.
ArkType 2.2 rend possible une adoption partielle. La documentation des intégrations d’ArkType montre que type() accepte n’importe quel validateur Standard Schema, seul ou au sein d’une définition d’objet, et l’infère et le vérifie comme une définition ArkType native. Vos schémas Zod 4 existants peuvent donc rester tels quels :
import * as z from "zod"
import { type } from "arktype"
const ZodDevice = z.object({ platform: z.enum(["android", "ios"]) })
const User = type({
name: "string",
device: ZodDevice, // Standard Schema validator nested in ArkType (2.1.28+)
})
Si votre contrainte principale est la taille du bundle plutôt que la vitesse, c’est plutôt du côté de Valibot qu’il faut regarder.
Quand passer de Zod à ArkType ?
La plupart des équipes disposant d’une base de code Zod stable et d’intégrations fonctionnelles ne devraient pas encore passer à ArkType. Le coût de la migration dépasse un gain de vitesse que le compilateur de Zod 4.5 compense déjà en partie. Essayez ArkType dans les cas suivants :
- Vous démarrez un nouveau projet et vos outils acceptent Standard Schema.
- Vous avez identifié un chemin critique, comme un endpoint à fort débit ou un traitement par lots, où le profilage révèle le coût de la validation.
- Vous souhaitez l’essayer sur un seul endpoint, en imbriquant vos schémas Zod existants dans des définitions ArkType au lieu de les réécrire.
Sinon, continuez à valider vos données avec Zod et essayez z.compile() sur les schémas les plus fréquemment exécutés.
Conclusion
Le principal atout d’ArkType est sa lisibilité : le schéma ressemble au type qu’il produit, et vous obtenez un validateur compilé et rapide. L’atout de Zod, c’est qu’il est déjà intégré à votre stack. Pour tester la différence à moindre coût, choisissez un endpoint fortement sollicité en validation, définissez-le avec ArkType 2.2 en y imbriquant vos schémas Zod existants, et profilez-le face à une version z.compile() du même schéma Zod avant de modifier quoi que ce soit d’autre.
FAQ
ArkType fonctionne-t-il dans Cloudflare Workers ou avec une Content Security Policy stricte ?
Oui. ArkType précompile la logique de validation avec new Function lors de l'instanciation d'un Type, et désactive automatiquement ce mécanisme dans les environnements qui ne prennent pas en charge new Function, comme Cloudflare Workers. Avec une CSP sans 'unsafe-eval', définissez l'option jitless sur true. Configurez-la depuis 'arktype/config' avant d'importer quoi que ce soit depuis 'arktype', afin que les mots-clés intégrés la prennent en compte. La validation fonctionne toujours, simplement sans les validateurs précompilés.
ArkType peut-il générer du JSON Schema comme Zod 4 ?
Oui. Chaque Type ArkType dispose d'une méthode toJsonSchema(), et ArkType 2.2 a ajouté le package @ark/json-schema pour la conversion inverse, qui transforme du JSON Schema en Types ArkType. Les fonctionnalités sans équivalent en JSON Schema, comme les morphs, les clés de type symbol ou Date, font lever une exception à toJsonSchema() par défaut, et une option fallback permet de traiter chaque cas. Zod 4 répond au même besoin avec z.toJSONSchema().
Quel est l'équivalent ArkType de la méthode transform() de Zod ?
ArkType appelle les transformations des morphs et les associe avec .pipe(), par exemple type('string').pipe(s => s.trim()). Des mots-clés de parsing intégrés comme 'string.json.parse' et 'string.numeric.parse' gèrent les conversions courantes sans callback. Si un morph lève une exception, ArkType considère que vous souhaitiez provoquer un plantage. Utilisez plutôt .pipe.try() pour transformer l'exception levée en résultat ArkErrors. Le type inféré reflète la sortie du morph.
ArkType peut-il lever une exception sur des données invalides comme la méthode parse() de Zod, au lieu de renvoyer des erreurs ?
Oui. Appeler out.throw() sur un résultat ArkErrors lève l'exception, et l'option globale onFail fait lever une exception à chaque Type en cas de données invalides : configure({ onFail: errors => errors.throw() }) depuis 'arktype/config'. Déclarez le même onFail dans l'interface globale ArkEnv afin que TypeScript sache que les appels ne renvoient plus ArkErrors. Le style par défaut, basé sur la valeur de retour, correspond à safeParse() de Zod, et onFail correspond à parse().
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k