Skip to main content

Command Palette

Search for a command to run...

[ 살펴보기 ] Zod - Object, Array and others

Updated
6 min readView as Markdown
[ 살펴보기 ] Zod - Object, Array and others
C

A developer living in Busan, Korea

Zod를 통해 primitive type 검사 뿐만 아니라 object나 array에 대한 validation 역시 할 수 있다.

Object

다음은 object를 validation하는 간단한 예제다.

import { z } from "zod";

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const testObj = {
  name: "test",
  age: 20,
};

const test = testSchema.parse(testObj);
/* 
    object property와 data type이 zod object schame와 
    일치 하므로 pass
*/

const testInvalidObj = {
  name: "test",
  age: "20",
};

const test = testSchema.parse(testInvalidObj);
// age property가 string이므로 error

만일 생성한 zod object schema를 기반으로 타입을 생성하고 싶다면 다음과 같이 할 수 있다.

type TestSchema = z.infer<typeof testSchema>;

생성한 object schema의 특정 property에 접근하고자 한다면 생성한 object schema의 shape property를 통해 접근할 수 있다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const ageTestSchema = testSchema.shape.age;

ageTestSchema.parse(20);

만약 생성한 object schema의 propery key를 기반으로 Zod enum을 생성하고자 한다면 object schema의 keyof method를 사용할 수 있다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const testSchemaKeys = testSchema.keyof();
// testSchemaKeys는 다음과 동일하다 z.enum(["name", "age"])

기존에 생성한 object schema에 다른 propery를 추가하여 확장하고 싶다면 extend method를 통해 확장할 수 있다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const extendedTestSchema = testSchema.extend({
  nation: z.string(),
});

const testInvalidObj = {
  name: "test",
  age: 20,
};

const test = extendedTestSchema.parse(testInvalidObj);
// nation property가 존재하지 않으므로 error

비슷한 method로 두 개의 schema를 합쳐 새로운 schema를 만드는 merge method가 있다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const newTestSchema = z.object({
  address: z.string(),
});

const mergedTestSchema = testSchema.merge(newTestSchema);

Typescirpt의 utility처럼 zod에서도 pick, omit, partial, required 등의 method를 제공한다. 우선 object schema의 특정한 property만 선택하거나 제외할 수 있는 pick과 omit method를 살펴보자.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
  address: z.string(),
});

const pickedTestSchema = testSchema.pick({ age: true });
const omittedTestSchema = testSchema.omit({ age: true });

const testPickedObj = {
  age: 20,
};

const testOmittedObj = {
  name: "test",
  address: "test addres",
};

const testPick = pickedTestSchema.parse(testPickedObj);
// validation pass

const testOmit = omittedTestSchema.parse(testOmittedObj);
// validation pass

Partial이나 deepPartial을 통해 object schema property 모두 optional로 전환할 수도 있다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
  address: z.string(),
});

const testPartialSchema = testSchema.partial();

const testObj = {
  name: "test",
  address: "test addres",
};

const test = testPartialSchema.parse(testObj);
/*
    partial schema를 통해 검사를 하므로 testObj에 
    age property가 없어도 validation pass
*/

schema의 모든 property를 optional로 만드는 것이 아닌 특정 property만 optional로 만들고 싶다면 partial method에 타겟 property를 인자로 넘긴다.

const testPartialSchema = testSchema.partial({ address: true });

주의할 점은 일반 partial method는 shallow level로 partial을 적용하므로 nested obejct로 구성된 object schema는 deepPartial을 통해 partial을 적용한다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
  location: z.object({
    latitude: z.number(),
    longitude: z.number(),
  })
});

const testPartialSchema = testSchema.deepPartial();

반면 required method는 object shema의 모든 property를 필수로 만든다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
  address: z.string().optional(),
});

const testRequiredSchema = testSchema.required();

const testObj = {
  name: "test",
  age: 20,
};

const test = testRequiredSchema.parse(testObj);
/*
 검사시 address property도 optional이 아닌 필수로 취급하므로
 validation fail
*/

schema의 모든 property를 required로 만드는 것이 아닌 특정 property만 required로 만들고 싶다면 required method에 타겟property를 인자로 넘긴다.

const testPartialSchema = testSchema.required({ address: true });

Zod는 parse를 통해 validation에 성공하면 parse method에 전달 했던 인자를 그대로 return한다. 만약 parse method에 object schema에 존재하지 않는 property를 함께 넣으면 validation이 성공했을 때 return되는 값 중에 schema에 존재하지 않는 property는 제외된다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
  address: z.string().optional(),
});

const testObj = {
  name: "test",
  age: 20,
  address: "test address",
  nationality: "test nationality",
};

const test = testSchema.parse(testObj);

/*
    {
        "name": "test",
        "age": 20,
        "address": "test address"
    }
*/

만약에 validation 성공 이후 return되는 값에 shema에 존재하지 않는 값도 포함시고 싶다면 parse method 이전에 passthrough method를 추가해 준다.

...
const test = testSchema.passthrough().parse(testObj);

/*
    {
        "name": "test",
        "age": 20,
        "address": "test address",
        "nationality": "test nationality" // nationality도 포함된다.
    }
*/

위에서 살펴보았듯이 zod는 default로 object shema에 존재하지 않는 property를 가진 object를 parse에 전달하면 return 결과에서 해당 property를 제외해버린다.

만약 object schema에 존재하지 않는 property를 전달하지 못하게 막고 싶다면 strict method를 parse method 전에 추가해준다. strict가 적용되면 shema에 존재하지 않는 property가 전달되면 오류가 발생한다.

const testSchema = z.object({
  name: z.string(),
  age: z.number(),
  address: z.string().optional(),
});

const testObj = {
  name: "test",
  age: 20,
  address: "test address",
  nationality: "test nationality",
};

const test = testSchema.strict().parse(testObj);
/*
    strict로 인해 schema 존재하지 않는 proprty가 전달되면
    오류 발생
*/

만약 schema에 존재하지 않는 property를 허용하되 해당 property의 데이터 타입을 검사해야 하는 경우라면 어떻게 해야할까? 그럴 때는 catchall method를 활용할 수 있다.

const testSchema = z
  .object({
    name: z.string(),
    age: z.number(),
    address: z.string().optional(),
  })
  .catchall(z.string());

const testObj = {
  name: "test",
  age: 20,
  address: "test address",
  nationality: "test nationality",
};

const testInvalidObj = {
  name: "test",
  age: 20,
  address: "test address",
  phone:123
};

const test = testSchema.parse(testObj);
/*
    catcheall의 요구 type이 string이고 testObj가 
    가지고 있는 추가 property인 nationality 역시 string이므로
    validation pass
*/

const testInvalid = testSchema.parse(testInvalidObj);
/*
    반명 testInvalidObj가 가지고 있는 추가 property인
    phone은 number type이고 catchall의 조건에 부합하지 못하므로
    validation fail
*/

참고로 catchall을 적용한 schema로 validation 검사를 하면 parse 이전에 strict가 적용되더라도 무시된다. 또한 validation이 성공했을 때 schema에 존재하지 않는 property도 return 값에 함께 포함된다.

...
const test = testSchema.parse(testObj);

/*
    {
        "name": "test",
        "age": 20,
        "address": "test address",
        "nationality": "test nationality"
    }
*/

Array

다음은 array data인지 validation하는 간단한 예제다.

const testSchema = z.array(z.string());

const testArr = ["test", "test2"];

const test = testSchema.parse(testArr);

Array schema를 아래와 같이 구성할 수도 있다.

const testSchema = z.string().array();
// z.array(z.string())와 동일하다.

다만 위와 같이 구성할 때 optional과 같은 추가 method를 연결한다면 적용하는 순서에 유의해야 한다.

z.string().optional().array(); // (string | undefined)[]
z.string().array().optional(); // string[] | undefined

Array validation시 empty array를 허용하지 않고 싶으면 nonempty method를 사용한다.

const testSchema = z.array(z.string()).nonempty();
// 또는 const testSchema = z.string().array().nonempty();

const testArr = [];

const test = testSchema.parse(testArr);
// empty array이므로 validation fail

Array의 length를 제한하고 싶다면 length, min, max method를 활용할 수 있다.

z.string().array().min(5); 
// array length가 5 이상일 때 pass

z.string().array().max(5); 
// array length가 5 이하일 때 pass

z.string().array().length(5); 
// array length가 5일 때만 pass

Promise

Zod를 통해 promise type역시 validation 가능하다. 일반 value validation과는 다르게 검사하는 value가 promise instance여야 한다.

const testSchema = z.promise(z.string());

testSchema.parse(Promise.resolve("test"));
// validation pass

testSchema.parse("test");
// validation error

InstanceOf

Zod를 통해 검사하는 value가 특정 class의 instance인지 확인할 때 instanceOf method를 사용할 수 있다.

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const testSchema = z.instanceof(Person);

const testVal = new Person("jack");

testSchema.parse(testVal);
// validation pass

Custom Schema

만약 zod에서 제공되는 valdiation외에 custom schema를 구성해야 한다면 다음과 같이 할 수 있다. 예를 들어 아래 코드는 숫자로 이루어진 문자열 마지막에 % 기호가 포함되어 있는 value만 pass하는 custom schema의 예제이다.

const testSchema = z.custom<`${number}%`>((val) => {
  return typeof val === "string" ? /^\d+%$/.test(val) : false;
});

const testPassVal = "11%";
testSchema.parse(testVal);
// validation pass

const testErrorVal = "11";
testSchema.parse(testVal);
// validation error

만약 custom schema의 validation error message를 직접 설정하고 싶다면 custom의 두 번째 인자로 error message를 전달해준다.

const testSchema = z.custom<`${number}%`>((val) => {
  return typeof val === "string" ? /^\d+%$/.test(val) : false;
}, "test custom error");

More from this blog

[ 살펴보기 ] TypeORM - Transactions, Migration

Transation Database 종류에 따라 detail한 부분은 차이점이 조금씩 있겠지만 각 sql statement는 개별적인 transaction block을 통해 실행되며 Database 설정에 따라 sql statement의 실행 결과가 자동으로 commit되어 영구히 적용되거나 commit을 직접 실행하기 전까지는 영구히 적용되지 않을 수 있다. 대부분의 경우 default로 sql statement 실행 결과가 자동으로 comm...

Feb 9, 20256 min read
[ 살펴보기 ] TypeORM - Transactions, Migration

Dev Diary

184 posts