JSON은 눈대중으로 쉽게 검증이 가능하고 작성도 매우 편하다. YAML도 매우 간단한 문법만 쓴다면 JSON만큼 쉽지만 JSON과 차별되는 다른 문법들을 쓰면 더 편리한 상황이나 YAML이 어떻게 파싱되는지 궁금한 상황이 있을 수 있다. 스펙을 읽고 그런 상황에 빠르게 대처할 수 있게 나름대로 정리를 해놓는다. 스펙 문서1가 길지만 예제 덕분에 어떻게든 읽힌다.

글이 너무 장황해지는 것을 막기 위해 일부 내용은 의미를 해치지 않는 선에서 축약하거나 생략했다.

예제들은 이해를 돕기 위해 whitespace와 line break를 다른 문자로 표기할 수도 있다.

문자 표기
tab (0x09)
line break (0x0A)
space (0x20) .

YAML 구조

Document

YAML은 0개 이상의 document 들로 이루어져 있다. 각 document들은 서로에 대해 완전히 독립적이며 ...---로 구분된다. 두 마커가 가진 다른 의미는 여기서 다루지 않는다.

gopkg.in/yaml.v3 기준, ---만 쓰는 것을 권장함

# no document
# one document
key: value
# two documents
key: value
---
null

document는 최상위에 있는 node 하나와 대응된다.

Node

node에는 세 가지 종류가 있다: Scalar, Sequence, Mapping.

  • Scalar — 0개 이상의 유니코드 문자로 표현된다.
    • YAML 문법은 Scalar 값이 가지는 타입에 대해 (예를 들어 string, integer인지 에 대해) 정해두지 않았다. Scalar는 단순히 유니코드 문자열이며 타입 정보는 tag2로 정의되거나 YAML 프로세서(프로그램 혹은 라이브러리)가 부여할 수 있다.
    • YAML 스펙은 YAML 프로세서가 JSON식 표현도 잘 해석하도록 강권하고 있으므로 Scalar의 값을 표기할 때 JSON처럼 써도 된다.
  • Sequence — node들의 순서있는 나열이다. JSON array와 비슷하다.
  • Mapping — key/value node 쌍의 순서없는 집합이다. JSON object와 다르게 유일성이 지켜지는 한 key에 아무 node가 올 수 있다.

YAML 표기법

node-styles

node를 표현할 수 있는 방식, style에는 두 가지가 있다: flow style, block style

  • block style 주요 특징

    • indentation(들여쓰기)이 구조를 결정한다.
    • child node는 항상 parent node보다 indent-level(들여쓰기의 길이)이 높아야 한다.
    • sibling node들은 항상 indent-level이 같아야 한다.
    • indentation에는 space(0x20)만을 쓸 수 있고 tab(0x09)은 쓸 수 없다.
  • flow style 주요 특징

    • JSON 표기법과 유사하다.
  • 예시

    # block style 표기
    
    -
    ·-
    ·····- element
    ---
    -
    ·-
    ·····- element
    - new
    ---
    -
    ·-
    ·····- element
    ·- new
    ---
    -
    ·-
    ·····- element
    ·····- new
    ---
    # INVALID
    -
    ·-
    ·····- element
    ···- new
    
    # 위와 동치인 flow style 표기
    
    [ [ [ "element" ] ] ]
    ---
    [ [ [ "element" ] ], "new" ]
    ---
    [ [ [ "element" ] "new" ] ]
    ---
    [ [ [ "element", "new" ] ] ]
    ---
    ERROR
    

    예시 - indentation

구조 요소들:

  • line prefixscalar 표기의 각 non-empty line은 line prefix로 시작된다.
    • block style line prefix = indent-level 만큼의 space
    • flow style line prefix = indent-level 만큼의 space와 그 뒤의 whitespaces
  • empty line — indent-level보다 적은 space로 시작하고 line break로 끝나는 line
    • 아무런 내용이 없는 line으로 보이지만 line break로 작용할 수 있다.
  • line folding — 긴 문자열을 표기 상 가독성을 위해 여러 줄에 걸쳐 표기하는 것을 가능케 한다.
    • 공통 규칙

      • 뒤에 empty line이 오는 line break들 중 첫번째 line break는 버려진다. 나머지 그러한 line break들은 content에 포함될 수 있다.
      • line break 뒤에 empty line이 없으면 그 line break는 space로 변환된다.
    • 공통 규칙의 적용 여부는 아래의 Block Folding 예시와 Flow Folding 예시를 참조한다.

    • Block Folding

      # (공통 규칙) 🍎 뒤의 line break는 버려진다.
      
      >-
      ··🍎↓
      ··↓
      ·↓
      
      ··🍊
      ··🍉
      
      "🍎\n\n\n🍊·🍉"
      

      예시 - block folding 1

      # (공통 규칙 예외) 🍊가 있는 line은 line prefix 뒤에 tab과 space가 있다.
      # 이 때, 🍎가 있는 line의 line break는 버려지지 않는다.
      
      >
      ··🍎·↓
      ·↓
      ···→🍊↓
      
      ··🍉↓
      
      "🍎 \n\n\t 🍊\n\n🍉\n"
      

      예시 - block folding 2

    • Flow Folding

      # flow scalar의 각 line의 앞과 뒤의 space/tab들은 모두 버려진다.
      # 그런 뒤 공통 규칙을 적용한다.
      
      "↓
      ··🍎·↓
      ·↓
      ····🍊↓
      ··🍉↓
      ·"
      
      # (중간 과정) space/tab 버린 후
      
      "↓
      🍎↓
      🍊↓
      🍉↓
      "
      
      "·🍎\n🍊\n🍉·"
      

      예시 - flow folding

Flow Style

Flow Scalar

  • flow scalar에는 세 가지 style이 있다: double-quoted, single-quoted, plain (unquoted)
  • 어떤 style이건 multi-line scalar는 flow maping의 implicit key로 쓰일 수 없다.

Double-quoted Scalar

  • 시작과 끝을 나타내는 " 쌍으로 표기한다.

  • escape sequence를 쓸 수 있으며 임의의 문자열을 표현할 수 있는 유일한 style이다.
    \"를 쓰기 위해선 escape 해야 한다.

  • multi-line double-quoted scalar

    • 여는 " 바로 뒤의 leading whitespace들과 닫는 " 바로 앞의 trailing whitespace들을 제외하고 각 line의 leading/trailing whitespace들은 모두 버려진다.
    • line break와 empty line들에 대해 flow line folding이 적용된다.
    • line break를 escape하여 content에서 제외시킬 수 있다. 이 때, 이 line break 앞의 trailing whitespace들은 보존된다.
    "···FOLDED·↓
    ···TO A SPACE,·→↓
    ·↓
    ···↓
    ··ESCAPE BREAK·→\↓
    ··\·→LAST LINE··"
    
    "···FOLDED TO A SPACE,\n\nESCAPE BREAK·\t·\tLAST LINE··"
    

    예시 - multi-line double-quoted flow scalar

Single-quoted Scalar

  • 시작과 끝을 나타내는 ' 쌍으로 표기한다.
  • escape sequence를 쓸 수 없다. 이런 이유로 single-quoted scalar가 표현할 수 있는 문자는 printable에 한정된다.
    대신 \"를 제약없이 쓸 수 있다.
  • multi-line single-quoted scalar
    • line break를 escape할 수 없다는 것을 제외하면 double-quoted scalar의 것과 유사하다.

Plain Scalar

  • 시작과 끝을 나타내는 기호가 없으며 escape sequence도 쓸 수 없다. 따라서 가장 제한적이고 문맥에 민감한 style이다.
  • plain scalar는 (첫번째 줄의/마지막 줄의) leading/trailing whitespace를 쓸 수 없다. 각 line의 leading/trailing whitespace들은 모두 버려진다.
  • node의 구조를 표현하기 위한 여러 특수목적 문자들({, # 등)로 시작 문자로 쓸 수 없다.
    • 단, ?, :, -들의 경우 바로 다음 문자가 애매함을 유발하지 않는다면 시작 문자로 쓸 수 있다.
  • multi-line plain scalar
    • leading/trailing whitespace에 대한 내용 빼고는 single-quoted scalar와의 것과 유사하다.

Flow Sequence

  • flow sequence는 JSON array와 표기법이 유사하다.
  • JSON과 다른 점들
    • 마지막 entry의 trailing comma를 허용한다.
    • entry에 아무 flow node가 올 수 있다.
[
"double
 quoted", 'single
           quoted',
plain
 text, [ nested ],
single: pair,
]
[ "double quoted",
  "single quoted",
  "plain text",
  [ "nested" ],
  { "single": "pair" } ]

예시 - flow sequence

Flow Mapping

  • flow mapping은 JSON object와 표기법이 유사하다.
  • JSON과 다른 점들
    • 마지막 entry의 trailing comma를 허용한다.
    • key와 value에 아무 flow node가 올 수 있다.
    • ? 표시자를 써서 explicit하게 key를 표기할 수 있다.
    • : 표시자를 생략할 수 있다. value는 empty가 된다.
    • : 앞뒤를 모두 생략할 수 있다. 생략한 쪽은 empty value를 가지게 된다.
  • (참고) flow sequecne 안에 single pair mapping를 중첩시킬 수 있다.
{
  ? explicit: 🍎,
  implicit: 🍊,
  ?
}
{ "explicit": "🍎",
  "implicit": "🍊",
  null: null }

예시 - flow mapping 1

{
  no value1,
  no value2:,
  : no key,
}
{ "no value1": null,
  "no value2": null,
  null: "no key" }

예시 - flow mapping 2

# “:” 뒤에 space가 강제되는 경우는?

{
"adj1":value,
adj2:value,
"empty1":,
empty2:
}
{ "adj1": "value",
  "adj2:value": null
  "empty1": null
  "empty2": null }

예시 - flow mapping 3

Block Style

Block Scalar

  • block scalar의 style에는 literalfolded가 있다.
    • literal style = | + block scalar header + line break + content
    • folded style = > + block scalar header + line break + content
    • foldedliteral과 다르게 line break가 버려지거나 space로 바뀔 수 있다.
    • style에 관계없이 escape sequence를 쓸 수 없다.
  • block scalar header: leading spaces, 마지막 나오는 line break 그 뒤의 empty line의 처리 옵션
    • indentation indicator, [1-9]

      • content indentation level = indentation indicator + indentation level 만큼 leading space들을 제외한다.
      • indentation indicator가 없으면 첫번째 non-empty line의 leading space의 개수가 content indentation level이 된다.
    • chomping indicator: strip, -

      마지막 line break와 뒤이어 나오는 모든 empty line들을 content에서 제외함

    • chomping indicator: keep, +

      마지막 line break와 뒤이어 나오는 모든 empty line들을 content에 포함시킴

    • chomping indicator: clip, 기본 동작

      마지막 line break는 포함되나 뒤이어 나오는 모든 empty line들은 제외됨

    • indentation과 chomping header들은 같이 쓸 수 있다.

예시들:

  • literal style

    # literal, no header, single-line
    
    - | # content indentation = 2
    ··🍎
    - | # content indentation = 4
    ····🍎
    - 
    
    ---
    # literal, no header, multi-line
    
    - | # content indentation = 3
    
    ··
    ·
    ···🍊
    
    ··
    ·····
    ····🍊
    
    ·
    -
    
    [ "🍎\n", "🍎\n", null ]
    ---
    [ "\n\n\n🍊\n\n\n··\n·🍊\n", null ]
    

    예시 - literal style 1

    # literal, indicator, single-line
    
    - |1 # content indentation = 1
    ··🍎
    - |2 # content indentation = 2
    ····🍎
    -
    
    ---
    # literal, indicator, multi-line
    
    - |2 # content indentation = 2
    
    ··
    ·
    ···🍊
    
    ··
    ·····
    ····🍊
    
    ·
    
    -
    
    [ "·🍎\n", "··🍎\n", null ]
    ---
    [ "\n\n\n·🍊\n\n\n···\n··🍊\n", null ]
    

    예시 - literal style 2

    # literal, multi-line
    
    - | # clip
    ····🍎
    ··
    ···
    -
    
    ---
    - |- # strip
    ····🍎
    ··
    ···
    -
    
    ---
    - |+ # keep
    ····🍎
    ··
    ···
    -
    
    
    [ "🍎\n", null ]
    ---
    [ "🍎", null ]
    ---
    [ "🍎\n\n\n", null ]
    

    예시 - literal style 3

  • folded style

    앞서 다룬 “block folding 1”, “block folding 2” 예시들도 참고하자.

    # (공통 규칙) 🍎 뒤의 line break는 뒤에 empty line이 없으므로 space로 변환됐다.
    
    > # content indentation = 1
    ·🍎↓
    ·🍊↓
    
    
    "🍎·🍊\n"
    

    예시 - folded style 1

    # 1. empty line들은 모두 line break가 된다.
    # 2. 🍎 뒤의 line break는 space로 folded
    #    line break 바로 뒤의 line이 non-empty이면서 exactly-indented 이기 때문
    # 3. 🍊 뒤의 line break는 버려짐
    #    line break 뒤에 나오는, 바로 뒤가 아닌, 첫번째 non-empty line이 exactly-indented 이기 때문
    # 4. 🍉 뒤의 line break는 그대로 남음
    #    line break 뒤에 나오는 첫번째 non-empty line이 more-indented 이기 때문
    # 5. 🍑🍇🍓는 more-indented 이기 때문에 뒤에 나오는 line break들은 그대로 남음
    # 6. 맨 마지막 line break는 chomping header의 영향을 받으며 이 예제의 경우 그대로 남음
    
    
    > # content indentation = 1
    
    ·🍎↓
    ·🍊↓
    
    ·🍉↓
    ···🍑↓
    
    ···🍇↓
    ···🍓↓
    
    ·🍌↓
    ·🍒↓
    
    "\n🍎·🍊\n🍉\n··🍑\n\n··🍇\n··🍓\n\n🍌 🍒\n"
    

    예시 - folded style 2

Block Sequence

  • block sequence는 node entry의 나열이다. 각 node entry는 앞에 - 표시자와 연속된 whitespace가 있다.

    # flow scalar "-one"
    -one
    
    ---
    - one
    
    ---
    -···one
    
    ---
    -··one
    
    -·two
    
    "-one"
    ---
    [ "one" ]
    ---
    [ "one" ]
    ---
    [ "one", "two" ]
    ---
    
    
  • 각 node entry는 완전히 비어있거나, 중첩된 block node거나 compact in-line notation일 수 있다. compact notation은 node 항목이 중첩된 block collection일 경우에 쓰인다. 이 경우 - 표시자와 그 뒤의 space들은 중첩된 block의 indentation으로 간주된다.

    - # EMPTY
    - |
    ·🍎
    -·- 🍊 # compact..
    ··- 🍊🍊 ..sequence
    -·A: 🍉 # compact mapping
    
    [
     null,
     "🍎\n",
     ["🍊", "🍊🍊"],
     {"A": "🍉"}
    ]
    

Block Mapping

  • block mapping은 key/value pair entry의 나열이다. value에는 아무 종류의 node가 올 수 있고 key도 mapping 내에서의 유일성이 만족되는 한 아무 종류의 node가 올 수 있다.
  • mapping entry는 여러 가지 방법으로 표기할 수 있다.
    • explicit notation — ? 표시자로 key를 표기하고 : 표시자로 value를 표기한다. key와 value 표기 시 (block sequence 장에서 소개된) compact in-line notation을 쓸 수 있다.

      ? explicit key # empty value
      ? |
        block key
      : - one # compact..
        - two # ..notation
      ? - compact
        - key
      
      
      { "explicit key": null,
        "block key\n": [ "one", "two" ],
        [ "compact", "key" ]: null }
      
    • implicit notation — ? 표시자를 생략하는 대신 key node는 한 줄로 밖에 표기를 못하며 길이는 1024로 제한된다.

      plain key: in-line value
      : # both empty
      "quoted key":
      - entry
      
      { "plain key": "in-line value",
        null: null,
        "quoted key": ["entry"] }
      
    • compact notation — explicit notation과 implicit notation 둘 다 compact in-line notation에 쓸 수 있다.

      - 🍎: apple
      - ? 🍇: purple
        : 🍉: wmelon
      
      [ { "🍎": "apple" },
        { "🍇": "purple" }: { "🍉": "wmelon" } ]
      

복잡한 YAML 예시들 (TODO)

key: # s-separate-lines
# s-separate-lines
# s-separate-lines
·|
·block, scalar, literal

마무리

여기서 다루지 않은 흥미로운 주제들: directives, node tags, node anchors, node aliases


  1. https://yaml.org/spec/1.2.2 ↩︎

  2. 여기선 다루지 않는다. 참고: https://yaml.org/spec/1.2.2/#24-tags ↩︎