hand sketched logo of electrons orbiting a nucleus

Zod vs Arktype

Arktype feels really nice and promising!

David Blass has a wondeful detailed comparison of Zod and Arktype written here.

My post is smaller in scope, providing a quick comparison of some common tasks in each library.

number

// Zod
const num = z.number();

// Arktype
const num = type('number');

Type inference

// Zod
const num = z.number();

// Arktype
const num = type('number');

object

// Zod
const Person = z.object({
  name: z.string(),
});

// Arktype
const Person = type({
  name: 'string',
});

Array

// Zod
const PersonList = z.array(Person);

// Arktype
const PersonList = type([PersonResult, '[]']);
// or
const PersonList = arrayOf(PersonResult);

Type inference

// Zod
const Person = z.object({
  name: z.string(),
});
type Person = z.infer<typeof Person>;

// Arktype
const Person = type({
  name: 'string',
});
type Person = typeof PersonList.infer;

Optional key

// Zod
const Person = z.object({
  name: z.string(),
  phoneNumber: z.string().optional(),
});

// Arktype
const Person = type({
  name: 'string',
  'phoneNumber?': 'string',
});

Default value

// Zod
const Person = z.object({
  name: z.string(),
  phoneNumber: z.string().default('555-555-5555'),
});

// Arktype
const FormWithDefault = type({
  name: 'string',
  // unclear if this is idiomatic Arktype
  phoneNumber: morph('string|undefined', (s) => s ?? '555-555-5555'),
});

Object indexed by string

// Zod
const Person = z.record(z.string());

// Arktype
const Person = type('string<string>');

Union of string literals

// Zod
const PrivateOrPublic = z.union([z.literal('private'), z.literal('public')]);

// Arktype
const PrivateOrPublic = type("'private'|'public'");

number with min/max length

// Zod
const PhoneNumber = z.string().min(5).max(20);

// Arktype
const PhoneNumber = type('5<string<20');

Transform data and extend

// Zod
const PersonZod = z
  .object({
    name: z.string(),
  })
  .transform((person) => ({
    ...person,
    lengthOfName: person.name.length,
  }));

// Arktype: note that in upcoming beta this syntax will change a bit
const PersonArktype = type([
  {
    name: 'string',
  },
  '|>',
  (person) => ({ ...person, lengthOfName: person.name.length }),
]);

Branded type

// Zod
const Positive = z
  .number()
  .refine((n) => n > 0, {
    message: 'Must be positive',
  })
  .brand<'Positive'>();

// Arktype, may change as API changes, see https://github.com/arktypeio/arktype/issues/741
const Positive = type('number>0' as Infer<Brand<number, 'Positive'>>);

// where Infer is a helper type from Arktype
// where Brand is the helper type
declare const brand: unique symbol;
type Brand<T, U> = T & { [brand]: U };