빠른 비교
| keyword | 통과 조건 |
|---|---|
oneOf | 여러 schema 중 정확히 하나만 통과 |
anyOf | 여러 schema 중 하나 이상 통과 |
allOf | 모든 schema를 동시에 통과 |
조합 방식
oneOf는 배타적인 선택지를 표현한다
oneOf는 후보 schema 중 정확히 하나만 통과해야 성공합니다. 두 schema를 모두 만족해도 실패합니다. 그래서 "A 타입 또는 B 타입"처럼 구분자가 확실한 union을 표현할 때 적합합니다. object 구조가 서로 겹치면 oneOf가 예상보다 자주 실패하므로, type 필드나 kind 필드처럼 분기용 discriminator를 두는 편이 안전합니다.
{
"oneOf": [
{
"properties": {
"kind": { "const": "email" },
"address": { "type": "string" }
},
"required": ["kind", "address"]
},
{
"properties": {
"kind": { "const": "sms" },
"phone": { "type": "string" }
},
"required": ["kind", "phone"]
}
]
}anyOf는 느슨한 대체 조건을 표현한다
anyOf는 후보 중 하나 이상만 통과하면 됩니다. 여러 후보를 동시에 만족해도 성공합니다. "이 값은 string이거나 number면 된다", "여러 인증 방식 중 하나만 만족하면 된다"처럼 겹침이 허용되는 조건에 맞습니다. 단, anyOf는 어떤 후보로 검증됐는지 하나로 확정하지 않으므로 코드 생성이나 UI form 생성에서는 oneOf보다 모호하게 보일 수 있습니다.
{
"anyOf": [
{ "type": "string", "minLength": 1 },
{ "type": "number", "minimum": 0 }
]
}allOf는 여러 제약을 합성한다
allOf는 모든 schema를 동시에 통과해야 합니다. 공통 필드 규칙과 추가 제약을 합칠 때 자주 씁니다. 다만 allOf는 object를 "상속"하거나 property를 자동 병합하는 문법이 아닙니다. 각 schema가 독립적으로 검증되고, 그 결과가 모두 true여야 통과하는 방식입니다.
{
"allOf": [
{
"type": "object",
"required": ["id"]
},
{
"properties": {
"id": { "type": "string" },
"active": { "type": "boolean" }
}
}
]
}서로 충돌하는 제약을 allOf에 넣으면 어떤 값도 통과하지 못합니다. 예를 들어 같은 위치에 type: "string"과 type: "number"를 동시에 요구하면 항상 실패합니다.
선택 기준
| 상황 | 적합한 선택 |
|---|---|
| 서로 배타적인 payload variant | oneOf |
| 여러 조건 중 하나 이상이면 충분 | anyOf |
| 공통 제약과 추가 제약을 모두 적용 | allOf |
| 후보 schema가 서로 많이 겹침 | anyOf 또는 discriminator 추가 |
| UI form이나 코드 생성 대상 schema | oneOf는 구분 필드와 함께 사용 |
주의할 점
oneOf는 "하나라도 맞으면 통과"가 아니라 "정확히 하나만 맞으면 통과"입니다. 두 후보 schema를 동시에 만족하면 실패합니다. 반대로 allOf는 schema 병합 문법이 아니라 모든 제약을 동시에 적용하는 검증 규칙입니다.
조합 keyword를 쓰기 전에 후보 schema끼리 겹치는지 확인해야 합니다. 특히 object variant는 required만으로는 구분이 약할 수 있습니다. 안정적인 검증 계약이 필요하면 kind, type, version 같은 고정 필드를 두고 const로 분기를 잠그는 방식이 명확합니다.
참고 링크
2 sources