YAML Ain't Markup Language[1]
yaml.org[2]1. 개요
기존에 주로 사용되던 포맷인 JSON의 불편함을 해소하기 위해 만들어진 superset이다.[3]2. 탄생 배경
JSON이 기존의 XML을 대체하고 널리 쓰이는 표현으로 받아들여졌지만, 이 또한 여러가지 단점을 가지고 있었다.[4]- 주석을 지원하지 않는다. 이 때문에 설정파일로 사용하기 어려워진다.
- 문법이 유연하지 않다.
-
모든 문자열에 따옴표가 강제되며,
"
( 쌍따옴표)만 사용해야 했기에"
는 일일이 이스케이핑해야 한다.[5] - 모든 프로퍼티마다 쉼표(,)로 구분해야 하며, trailing comma는 불가능하다. 이는 git diff를 지저분하게 만들고, 수시로 편집해야 하는 설정파일에서는 상당히 효율이 떨어지는 작업이다.
- 중괄호를 모두 닫아야 한다. 만약 한쪽을 열고 닫지 않는다면 당연히 에러가 나며, 객체나 배열을 새로 만들 때마다 불필요한 기호가 추가되어 길이가 길어진다.
- true, null등의 리터럴에서 오타가 나기 쉽고, 원하는 형식대로 간단히 쓸 수가 없다.
- 문자열 안에서 이스케이프 문자를 처리하기가 복잡하다.
- 반복적으로 쓰이는 값을 일일이 수정하거나 관리하기 어렵다. 특히 설정파일에서는 이런 문제가 심해진다.
- 타입을 명시할 방법이 존재하지 않는다.
3. 문법
#!syntax yaml
# yaml은 주석을 지원한다!
propA: lorem ipsum # 문자열에 따옴표는 선택사항이다.
# 프로퍼티 이름에 공백이 들어가도 상관없다.
my name is: 'namu\nwiki' # 작은따옴표(')와 큰따옴표(")를 모두 지원하며, 따옴표로 문자열을 감쌀 시 \n등의 이스케이프 문자를 사용할 수 있다.
without quotes: namu\nwiki # 따옴표가 없다면 일반 문자로 취급된다. 즉 json에서는 "namu\\nwiki" 처럼 변환된다.
#colon: string:
# 주석처리한 위 예제는 파싱에러가 발생한다.
colon: "string:" # : 등의 yaml에서 사용하는 기호를 사용하려고 할 때도 따옴표가 필요하다.
# 긴 문장(개행을 포함한 문장)을 표현할 때는 >, | 를 사용한다.
# > 를 사용하면 개행, 빈줄 없이 한 문장으로 인식한다. (끝에 공백 포함)
lorem ipsum1: >
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Quisque dictum lorem a tempor feugiat. Cras sit amet semper mi.
Phasellus dignissim lobortis nisl, id varius metus.
# 개행(\n)과 빈 줄(\n\n)을 모두 인식한다.
lorem ipsum2: |
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Quisque dictum lorem a tempor feugiat. Cras sit amet semper mi.
Phasellus dignissim lobortis nisl, id varius metus.
# >와 |의 끝에는 +또는 -를 사용할 수 있다.
# 다만 >와 |는 각각 >+와 |+의 단축 표현이기 때문에 명시적으로 +를 쓰는 경우는 거의 없다.
# >, |의 끝에 -를 사용하면 마지막에 개행(\n)을 포함하지 않는다.
# 위 예제를 각각 >- 와 |- 로 시험해 보자.
한글: 나무위키 # 유니코드 문자를 지원한다.
number: 3 # 숫자로 변환 가능한 문자라면 숫자로 인식한다.
PI: 3.141592653 # 소수를 지원한다.
float: 123.45678e+05 # 부동소수점을 지원한다.
not number: "3" # 따옴표로 감싼다면 무조건 문자열로 인식한다.
# .을 사용해 특수한 숫자를 표현할 수 있다.
# 이 숫자들의 경우 json으로 변환시 보통 null로 변환된다.
positive infinity: .inf # INF, Inf등의 일반적인 대소문자 표현을 모두 인식한다.
negative infinity: -.inf # -를 붙이면 음의 무한대를 표현할 수 있다.
not a number: .NaN # NaN, nan, NAN등을 모두 인식한다.
quarter: .25 # 문자가 아닌 숫자가 오면 소수점으로 인식한다. 이 경우는 0.25로 평가된다.
bool: true # true, false의 json식 불리언 값을 사용 가능하다.
yet another bool: yes # 추가로 yes, no등도 사용 가능하다. YAML 1.2부터는 지원하지 않는다.
light: Off # on, off등도 사용 가능하다. YAML 1.2부터는 지원하지 않는다.
python style bool: False # True, TRUE, False, FALSE 등의 일반적인 대소문자 표현을 인식한다.
not yet bool: TruE # "TruE" 로 평가된다.
null value: null # null을 사용 가능하다.
null shorthand: ~ # ~기호는 null의 단축 표현이다.
date: 2005-12-12 # ISO시간 포맷을 사용할 수 있다.
date2: 1234-56-78T12:34:56
date3: 12:34:56 # 시각은 초로 평가된다.
# 배열의 아이템은 -(하이픈)으로 구분한다.
array:
- apple
- banana
- 12345
- a: b # array의 a라는 속성에 b가 담긴 것이 아니라, array의 아이템 중 { "a": "b" }라는 객체가 있다는 뜻이다.
json style array: ["apple", banana, 12345, a: b] # 한줄로 쓸 때는 []를 사용한다.
json style array2: [
apple,
banana,
12345,
a: b, # 마지막의 ,(trailing comma)는 선택사항이다. 단, 모든 아이템은 ,로 구분되어야 한다.
]
# 객체는 들여쓰기와 :으로 구분한다.
object:
name: namu wiki
type: dictionary
primary color: # json과 마찬가지로 중첩이 가능하다.
gradient:
start: 0x00A495 # hex 숫자를 사용 가능하다.
end: 0x13AD65
wordmark: 0x614D42
header: 0x008275
# json스타일도 사용 가능하다.
json style object: {
name: namu wiki, # json스타일로 쓸 경우 ,가 필요하다.
"type": "dictionary" # 마지막의 ,(trailing comma)는 선택사항이다.
}
"quoted property name": value # 키에 따옴표를 사용해도 문제는 없다. 주로 :등의 문자가 있을 때 사용한다.
a1: b # 가능하다.
a2 : b # 가능하다.
#a3:b # 불가능하다. 콜론(:)과 값 사이는 띄어쓰기가 존재해야 한다.
# 단, json과의 호환성을 위해 존재하는 json식 표기에서는 키가 따옴표로 감싸져 있다는 조건 하에 붙혀서 쓸 수 있다.
#"a4":b # 불가능하다.
json style: {"a5":b} # 가능하다.
# key1을 가진 객체, key2를 가진 객체... 각각이 배열에 하나씩 담긴 것으로 평가된다.
arr:
- key1: value
- key2: value
- key3: value
- key4: value
# key1, key2를 가진 객체와 key3, key4를 가진 객체가 각각 배열에 담긴 것으로 평가된다.
arr2:
- key1: value
key2: value
- key3: value
key4: value
a6: # 가능하다. 이 경우 값에는 암묵적으로 null이 들어간다.
# 위 예제에 모두 null이 들어간다면 다음과 같이 쓸 수 있다.
arr3:
- key1:
key2:
- key3:
key4:
# 또한 '배열 안의 객체'는 주로 다음처럼도 표기한다 (완전히 동일한 표현이지만 포맷터 설정에 따라 다르게 나올 수 있다.)
arr4:
-
key1:
key2:
-
key3:
key4:
# ?로 키를 표현할 수 있다.
? key # key라는 이름의 키를 선언한다. 값은 암묵적으로 null이 들어간다.
? another key
: value # value라는 값을 할당한다. 즉 "another key": "value" 처럼 평가된다.
# 즉 위의 예제는 다음처럼 쓸 수도 있다.
arr5:
-
? key1
? key2
-
? key3
? key4
deep array:
- - - - - value # 배열은 한번에 여러번 중첩 할 수도 있다.
# yaml은 반복적인 작성을 피하기 위한 anchor라는 문법을 지원한다.
long long string: &anchor Lorem ipsum dolor sit amet, consectetur adipiscing elit.
reuse anchor: *anchor # Lorem ipsum dolor... 로 평가된다. 즉, 값을 재사용할 수 있다.
# 객체에도 사용할 수 있다.
some object: &object-anc
key: value
another key: another value
duplicate object: *object-anc # some object와 똑같은 내용이 들어간다.
extended object:
<<: *object-anc # some object의 내용을 기반으로 새 오브젝트를 만든다.
another key: updated value # 새 내용으로 덮어씌운다.
new key: new value # 새 필드를 작성한다.
# extended object는 최종적으로 { "key": "value", "another key": "updated value", "new key": "new value" } 로 평가된다.
# 이 동작은 자바스크립트의 스프레드 문법 { ...obj, key: value } 와 유사하다.
# 배열에서도 사용할 수 있다.
original array: &arr
- 1
- 2
copied array: *arr # array merge(<<:)는 지원하지 않는다.
# yaml은 한 파일에 여러 문서를 작성할 수 있다.
# -(대시)문자 3개로 새로운 스트림을 시작한다.
---
a: b # 기존과 다른 문서이기 때문에 키값이 겹쳐도 상관없다.
... # .(마침표)문자 3개로 스트림을 종료한다. (선택사항이며, 대부분 ...가 없어도 문제없이 인식한다)
# 보통 한 파일에 한 문서를 넣는 경우가 많아 ---로 시작하고 ...로 끝내는 것이 가장 이상적인 방법이지만, 대부분의 경우 둘을 생략할 수 있다.
# %로 시작하는 특수한 지시자로 문서에 설명을 추가할 수 있다.
%YAML 1.2 # %YAML은 현재 문서의 yaml버전을 명시한다.
%TAG !org! tag:yaml.org,2002: # %TAG는 외부에서 가져올 태그의 prefix를 명시한다.
---
must be string: !org!str 1234 # "1234"로 평가된다.
# !!는 !<tag:yaml.org,2002:(tagname)>: 의 단축 표기이기 대문에 다음과 같이 표현도 가능하다.
also string: !!str true # "true"로 평가된다.
empty object: !<tag:yaml.org,2002:map> # 태그 선언과 단축 표기법 둘다 사용하지 않을 수 있다.
key: value
# 태그로 문서의 자료형을 정의할 수 있다.
--- !!seq
- XML: Extensible Markup Language
- JSON: JavaScript Object Notation
- YAML: Yet Another Markup Language
# json의 superset이기 때문에, 일반적인 json표현을 그대로 사용할 수도 있다.
---
{
"key": "value",
"array": [
[1, 2, 3],
["apple", "banana", "orange"]
]
}
...
# yaml식 표현과 함께 사용할 수 있다.
---
- [name, avg: 547.2]
- [&bm {? namu : wiki, empty: }]
- [~, "this is string", *bm]
- { <<: *bm, empty: !!seq [] }
...
json처럼 숫자, 문자열, null, bool(참, 거짓), 배열 객체의 기본 타입이 존재하며[6], yaml문서는 이 타입의 조합들로 이루어진다.
일반 문서에 a: b형식으로 작성하면 그 순간부터 문서 전체가 하나의 객체로 간주되며, 단순히 값만 쓰거나 모든 줄이 -로 시작하면 각각 맞는 타입이나 배열로 인식된다. 즉, json처럼 매 문서마다 {}[7]로 감싸줄 필요가 없어 간결해진다.
들여쓰기를 원칙으로 하는 것이 특징이며, 배열과 객체 모두 들여쓰기를 사용한다.[8]
4. 특징
4.1. 장점
JSON의 완전한 상위 호환이기 때문에 기존 json문서를 그대로 yaml파일로 사용하거나, 원하는 부분만 손볼 수 있다. 반대로 yaml을 json으로 변환[9]할 수도 있다.이런 특징 덕분에 기존에 json을 사용하던 중이라면 금세 익힐 수 있으며 json만 지원하던 환경에서도 점차 yaml을 빠르게 지원해나가는 추세이다. 만약 지원이 없더라도 사용자가 직접 쉽게 변환해서 사용할 수 있다.
또한 XML, JSON등과 비교해 압도적으로 간결하다. 아래는 동일한 설정 파일을 각각 xml, json, yaml로 비교한 것이다.
#!syntax xml <?xml version="1.0" encoding="UTF-8" ?>
<root>
<name>Rust</name>
<on>
<push>
<branches>master</branches>
</push>
<pull_request>
<branches>master</branches>
</pull_request>
</on>
<env>
<CARGO_TERM_COLOR>always</CARGO_TERM_COLOR>
</env>
<jobs>
<build>
<runs-on>ubuntu-latest</runs-on>
<steps>
<uses>actions/checkout@v2</uses>
</steps>
<steps>
<name>Build</name>
<run>cargo build --verbose</run>
</steps>
<steps>
<name>Run tests</name>
<run>cargo test --verbose</run>
</steps>
</build>
</jobs>
</root>
#!syntax json {
"name": "Rust",
"on": {
"push": {
"branches": [
"master"
]
},
"pull_request": {
"branches": [
"master"
]
}
},
"env": {
"CARGO_TERM_COLOR": "always"
},
"jobs": {
"build": {
"runs-on": "ubuntu-latest",
"steps": [
{
"uses": "actions/checkout@v2"
},
{
"name": "Build",
"run": "cargo build --verbose"
},
{
"name": "Run tests",
"run": "cargo test --verbose"
}
]
}
}
}
#!syntax yaml
name: Rust
on:
push:
branches: [master]
pull_request:
branches: [master]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
단순히 길이가 짧은 것뿐만 아니라, 불필요한 따옴표나 기호 등이 사라져 가독성이 높기 때문에 사람이 읽거나 편집하기 쉽다.
또한 json과는 다르게 타입을 명시해 줄 수 있어 불필요한 실수를 피할 수 있다.
문서를 구분하는 기능도 존재해 한 파일에 여러 문서를 작성할 수 있다. json으로 생각하면 한 파일에 여러 {}(문서 루트)를 넣는다고 생각할 수 있다. 예를 들어, 아래와 같은 json 코드는 실제로는 불가능하다.
#!syntax json {
"a": "b"
}
{
"key": "value"
}
yaml에서는 다음과 같이 표현할 수 있다.#!syntax yaml
---
a: b
...
---
key: value
...
[* 더 간단하게는 다음과 같이 쓸 수 있다. #!syntax yaml
a: b
---
key: value
]문서를 구분하는 기능은 일반적으로 유용하지는 않지만, 여러 설정을 한 파일로 모은다거나 한 파일에서 원하는 위치의 문서만 사용하는 등 다양하게 할용될 수 있다.
4.2. 단점
데이터 직렬화가 불편하다. 아예 불가능하지는 않고 이스케이핑을 사용할 수는 있겠지만 그럴 바에는 JSON을 사용하는 편이 낫다. 따라서 데이터 전송, API등에서는 거의 쓰이지 않는다.워낙 문법이 복잡한 것에 비해 자주 쓰이지 않는 부분이 많다. 사용하는 입장이라면 그냥 배우지 않고 쓰면 그만이지만 언젠가 복잡한 문법으로 쓰인 yaml문서를 읽게 되었을 때 문제가 생길 수 있다. 즉 기본만 배워서 쓸 수는 있지만 지식의 격차가 생기게 되며, 그렇다고 배우자니 결국엔 실제로는 거의 쓰지도 않는 내용만 배우게 된다. 게다가 복잡한 문법 탓에 파서를 만들기도 더욱 어려워진다.
특히 괄호 대신 들여쓰기를 사용하는 것 때문에 불편할 수 있다. 무조건적인 단점이라기 보다는 어떤 언어를 써왔느냐에 따라 친숙할 수도 있고 불편할 수도 있는 부분이다.[10] 물론 굳이 쓰고 싶다면 중괄호를 사용할 수는 있다.
객체와 배열의 문법이 비슷하고 시작과 끝을 알기가 헷갈려 원치않는 결과가 나올 수 있다. 특히나 배열과 객체가 중첩되면[11] 상당히 읽기가 힘들어진다.
5. 사용 분야
일반적으로 설정파일로 사용하기에 더할 나위없이 좋은 형식이기 때문에 여러 프레임워크나 CI툴에서 설정파일로 쓰이고 있다.-
Flutter:
dart패키지를 관리하기 위해
pubspec.yaml
에 설정을 저장한다. -
devOps
IaaC(Infra as a Code)원칙에 따라 많은 클라우드 리소스를 설정파일 하나로 제어하는 방식이 각광받고 있으며, 이 경우 주로 yaml이 사용된다. - 수많은 CI서버에서 설정파일로 사용된다.
-
GitLab CI: 루트에 위치한
.gitlab-ci.yml
파일을 설정파일로 사용한다. -
trevisCI: 루트에 위치한
.trevis.yml
파일로 실행할 작업들을 지정한다. -
GitHub Action: 설정 파일이
.github/workflows/<워크플로 이름>.yml
로 yaml형식을 사용한다. -
sourcehut: 루트에 위치한
.build.yml
파일을 사용한다. -
CircleCI:
.circleci/config.yml
파일을 사용한다. - Docker: compose파일을 yaml로 작성하고 배포한다.
- 쿠버네티스: 지금껏 어떤 프레임워크보다 yaml을 광범위하게 사용하는 프레임워크 중 하나이다. 기본적인 팟, 레플리카, 디플로이먼트 등 모든 내부 오브젝트를 yml문서로 묘사하며, yaml고유 기능 중 하나인 문서 스트림을 사용해 클러스터 전체의 설정을 파일 하나로 관리할 수도 있다.[12] kubectl이나 GKE 클라우드 콘솔 등은 yaml형식 출력을 제공하기도 한다![13]
- Ansible: 자동화 툴 중 하나로, 설정 파일로 yaml을 사용한다.
-
prometheus: 설정파일로
prometheus.yml
파일을 사용한다. 파일 기반 SD(service discovery)의 경우에도 yaml과 json 포맷을 지원한다. - Sublime Text: 설정 파일로 사용할 수 있다.
- 마인크래프트 플러그인에서 많이 사용한다.
- Lock files
- yarn: yarn.lock파일이 yaml형식이다. 이 경우 개발자가 직접 작성하는 부분이 아닌 내부적으로 데이터를 저장하는 용도라는 점이 다르다.
-
PNPM: yarn과 비슷하게 lock file로
pnpm-lock.yaml
을 사용한다. -
스프링 부트: yaml 방식의 설정 파일을 사용할 수 있다. (
application.properties -> application.yaml
)
물론 일반 스프링에서도 별도로 설정할 수 있다면 사용할 수 있다. - OpenAPI(전 Swagger): API문서를 yaml또는 json으로 작성할 수 있다. 다만 doc-comment(annotaton)으로 코드 내에 주석으로 삽입하는 방식의 경우 yaml 형식이 더욱 선호되는 편이다. 특징으로, 동일하게 반복되는 스키마를 참조하는 방법으로 yaml의 앵커 문법 대신 json-reference를 사용한다. 또한 swagger-ui의 경우 yaml파일을 서버로 직접 요청해 프런트에서 문서를 렌더링할 수 있다.
- Unity에서 내부적으로 에셋 메타데이터를 기록하는 파일[14]에서 사용된다. #
5.1. 다양한 환경에서의 YAML처리
- Python: pyyaml 수많은 yaml라이브러리 중 가장 유명하다.
- JavaScript: js-yaml
그 외에도 yaml.org나 https://github.com/yaml/에도 여러 언어로 된 라이브러리가 존재한다.
6. 기타
- 'PHP: Hypertext Preprocessor'의 PHP나 'Linux Is Not UniX'의 Linux처럼 개발자들이 흔히 농담으로 사용하는 재귀약자를 사용해 지어졌다. 처음에는 Yet Another Markup Language였으나 마크업을 강조하는 이름 대신 Yaml Ain't Markup Language로 바뀌었다.
- 주로 야믈, 또는 야말과 비슷하게 발음한다.
-
.yaml
과.yml
둘 다 사용 가능한 확장자이다.
- 워낙 json과 비슷한 구조이기 때문에 json을 yaml로, 또는 그 반대로 변환해 주는 툴이 많다. json2yaml이나 convert-yaml-to-json 등의 온라인 툴도 많이 존재한다.
[1]
Yet Another Markup Language이라고도 한다.
[2]
사이트 자체가 유효한 yaml문서이다.
[3]
즉 일반적인 json 문서를 확장자만 바꾸어도 유효한 yaml문서이다.
[4]
단, 현재는 JSON-Schema, json5, json reference($ref)등의 기술로 인해 표준 JSON을 확장하는 방식으로 아래의 여러 문제점들을 상당 부분 해결했다. 자세한 내용은
JSON문서 참고.
[5]
두 따옴표를 모두 지원한다면 전체는
'
로 감싸고 내부에서 이스케이핑 없이 "를 사용할 수 있다.
[6]
그 외에 Date등의 타입이 있지만 자주 쓰이지는 않는다.
[7]
root object
[8]
-로 구분한다.
[9]
몇몇 데이터 타입이 사라지긴 하지만, 일반적인 경우엔 1:1수준으로 변환 가능하다.
[10]
이는 yaml이 루비 생태계에서는 자주 쓰이는 이유이기도 하다.
[11]
객체의 배열 같은 경우 - 다음에 개행을 한번 더 해줘야 읽기 쉬워진다.
[12]
각 기능별 분리를 위해 그다지 권장되지는 않는다. 예를 들어 서비스(로드밸런서)파일들과 디플로이먼트 파일들은 각 폴더로 분리해서 관리하는 것이 좋다.
[13]
json이 주로 machine-readable파일에 주로 쓰이고, yaml은 주로 쓰기(작성) 전용으로만 사용되는 것에 대조적이다.
[14]
확장자가 .yaml
이 아니라 .meta
이다.