NimYAML Home Testing Ground Docs: Overview Serialization Modules NimYAML 2.x Source on GitHub

NimYAML

Introduction

NimYAML is a pure YAML implementation for Nim. It is able to read from and write to YAML character streams, and to serialize from and construct to native Nim types. It exclusively supports YAML 1.2.

Source code can be found on GitHub. You can install it with Nimble:

nimble install yaml

You can find a conceptual overview of the library here and an overview over the library's API here.

NimYAML 2.x

NimYAML 2.0.0 introduced some breaking changes. Read about migrating to the new version here.

Quickstart

Dumping Nim objects as YAML

code.nim out.yaml
import yaml, streams
type Person = object
  name : string
  age  : int32

var personList = newSeq[Person]()
personList.add(Person(name: "Karl Koch", age: 23))
personList.add(Person(name: "Peter Pan", age: 12))

var s = newFileStream("out.yaml", fmWrite)
Dumper().dump(personList, s)
s.close()
- name: Karl Koch
  age: 23
- name: Peter Pan
  age: 12

Loading Nim objects from YAML

code.nim in.yaml
import yaml, streams
type Person = object
  name : string
  age  : int32

var personList: seq[Person]
var s = newFileStream("in.yaml")
load(s, personList)
s.close()
- { name: Karl Koch, age: 23 }
- { name: Peter Pan, age: 12 }

Customizing output style

… via style pragmas

code.nim out.yaml
import yaml, yaml/style, streams

type
  Strings = object
    first {.scalar: ssSingleQuoted.}: string
    second {.scalar: ssLiteral.}: string
    third {.scalar: ssDoubleQuoted.}: string
  
  Numbers {.collection: csFlow.} = object
    start, stop: int32
  
  Root = object
    strings: Strings
    numbers: Numbers
    blockNumbers {.collection: csBlock.}: Numbers

var root = Root(
  strings: Strings(
    first: "foo", second: "bar\n", third: "baz"
  ),
  numbers: Numbers(start: 0, stop: 23),
  blockNumbers: Numbers(start: 23, stop: 42)
)

var s = newFileStream("out.yaml", fmWrite)
Dumper().dump(root, s)
s.close()
strings:
  first: 'foo'
  second: |
    bar
  third: "baz"
numbers: {
    start: 0,
    stop: 23
  }
blockNumbers:
  start: 23
  stop: 42

… by customizing the Dumper

code.nim out.yaml
import yaml, streams
type Person = object
  name: string
  age: int32

var personList = newSeq[Person]()
personList.add(Person(name: "Karl Koch", age: 23))
personList.add(Person(name: "Peter Pan", age: 12))

var s = newFileStream("out.yaml", fmWrite)
var dumper = explanatoryDumper()
dumper.presentation.indentationStep = 3
dumper.presentation.newlines = nlLF
dumper.presentation.outputVersion = ov1_1
dumper.dump(personList, s)
s.close()
%YAML 1.1
%TAG !n! tag:nimyaml.org,2016:
--- !n!system:seq(tag:nimyaml.org;2016:custom:Person)
[
   !n!custom:Person {
      ?  !n!field "name"
      :  !!str "Karl Koch",
      ?  !n!field "age"
      :  !n!system:int32 "23"
   },
   !n!custom:Person {
      ?  !n!field "name"
      :  !!str "Peter Pan",
      ?  !n!field "age"
      :  !n!system:int32 "12"
   }
]

Dumping reference types and cyclic structures

code.nim out.yaml
import yaml, streams
type
  Node = ref NodeObj
  NodeObj = object
    name: string
    left, right: Node

var node1, node2, node3: Node
new(node1); new(node2); new(node3)
node1.name = "Node 1"
node2.name = "Node 2"
node3.name = "Node 3"
node1.left = node2
node1.right = node3
node2.right = node3
node3.left = node1

var s = newFileStream("out.yaml", fmWrite)
Dumper().dump(node1, s)
s.close()
--- &a
name: Node 1
left:
  name: Node 2
  left: !!null ~
  right: &b
    name: Node 3
    left: *a
    right: !!null ~
right: *b

Loading reference types and cyclic structures

code.nim in.yaml
import yaml, streams
type
  Node = ref NodeObj
  NodeObj = object
    name: string
    left, right: Node

var node1: Node

var s = newFileStream("in.yaml")
load(s, node1)
s.close()
--- &a
name: Node 1
left:
  name: Node 2
  left: ~
  right: &b
    name: Node 3
    left: *a
    right: ~
right: *b

Defining a custom tag uri for a type

code.nim out.yaml
import yaml, streams
type Mob = object
  level, experience: int32
  drops: seq[string]

setTag(Mob, Tag("!Mob"))
setTag(seq[string], Tag("!Drops"))

var mob = Mob(level: 42, experience: 1800, drops:
    @["Sword of Mob Slaying"])
var s = newFileStream("out.yaml", fmWrite)
var dumper = Dumper()
dumper.serialization.tagStyle = tsAll
dumper.serialization.handles = initNimYamlTagHandle()
dumper.dump(mob, s)
s.close()
%TAG !n! tag:nimyaml.org,2016:
--- !Mob
!n!field level: !n!system:int32 42
!n!field experience: !n!system:int32 1800
!n!field drops: !Drops [!!str Sword of Mob Slaying]

Dumping Nim objects as JSON

code.nim out.yaml
import yaml, streams
type Person = object
  name : string
  age  : int32

var personList = newSeq[Person]()
personList.add(Person(name: "Karl Koch", age: 23))
personList.add(Person(name: "Peter Pan", age: 12))

var s = newFileStream("out.yaml", fmWrite)
jsonDumper().dump(personList, s)
s.close()
[
  {
    "name": "Karl Koch",
    "age": 23
  },
  {
    "name": "Peter Pan",
    "age": 12
  }
]

Loading Nim objects from JSON

code.nim in.yaml
import yaml, streams
type Person = object
  name : string
  age  : int32

var personList: seq[Person]

var s = newFileStream("in.yaml")
load(s, personList)
s.close()
[
  {
    "name": "Karl Koch",
    "age": 23
  },
  {
    "name": "Peter Pan",
    "age": 12
  }
]

Processing a Sequence of Heterogeneous Items

… with variant objects

code.nim in.yaml
import yaml, streams
type
  Person = object
    name: string

  ContainerKind = enum
    ckString, ckInt, ckBool, ckPerson, ckNone

  # {.implicit.} tells NimYAML to use Container
  # as implicit type.
  # only possible with variant object types where
  # each branch contains at most one object.
  Container {.implicit.} = object
    case kind: ContainerKind
    of ckString:
      strVal: string
    of ckInt:
      intVal: int
    of ckBool:
      boolVal: bool
    of ckPerson:
      personVal: Person
    of ckNone:
      discard

setTag(Person, Tag("!Person"))

var list: seq[Container]

var s = newFileStream("in.yaml")
load(s, list)
s.close()

assert(list[0].kind == ckString)
assert(list[0].strVal == "this is a string")
# and so on
- this is a string
- 42
- false
- !!str 23
- !Person {name: Trillian}
- !!null

… with the YamlStream API

code.nim in.yaml
import yaml, yaml/data, yaml/parser, yaml/hints, streams
type Person = object
  name: string

setTag(Person, Tag("!Person"), yTagPerson)

var
  s = newFileStream("in.yaml", fmRead)
  yamlParser = initYamlParser()
  events = yamlParser.parse(s)
  context = initConstructionContext(events)

assert events.next().kind == yamlStartStream
assert events.next().kind == yamlStartDoc
assert events.next().kind == yamlStartSeq
var nextEvent = events.peek()
while nextEvent.kind != yamlEndSeq:
  var curTag = nextEvent.properties().tag
  if curTag == yTagQuestionMark:
    # we only support implicitly tagged scalars
    assert nextEvent.kind == yamlScalar
    case guessType(nextEvent.scalarContent)
    of yTypeInteger: curTag = yTagInteger
    of yTypeBoolTrue, yTypeBoolFalse:
      curTag = yTagBoolean
    of yTypeUnknown: curTag = yTagString
    else: assert false, "Type not supported!"
  elif curTag == yTagExclamationMark:
    curTag = yTagString
  case curTag
  of yTagString:
    var s: string
    context.constructChild(s)
    echo "got string: ", s
  of yTagInteger:
    var i: int32
    context.constructChild(i)
    echo "got integer: ", i
  of yTagBoolean:
    var b: bool
    context.constructChild(b)
    echo "got boolean: ", b
  of yTagPerson:
    var p: Person
    context.constructChild(p)
    echo "got Person with name: ", p.name
  else: assert false, "unsupported tag: " & $curTag
  nextEvent = events.peek()
assert events.next().kind == yamlEndSeq
assert events.next().kind == yamlEndDoc
assert events.next().kind == yamlEndStream
s.close()
- this is a string
- 42
- false
- !!str 23
- !Person {name: Trillian}