# module YAML

##
Overview

The YAML module provides serialization and deserialization of YAML version 1.1 to/from native Crystal data structures, with the additional independent types specified in http://yaml.org/type/

###
Parsing with `#parse` and `#parse_all`

`YAML.parse` will return an `Any`, which is a convenient wrapper around all possible YAML core types, making it easy to traverse a complex YAML structure but requires some casts from time to time, mostly via some method invocations.

    require "yaml"

    data = YAML.parse <<-END
             ---
             foo:
               bar:
                 baz:
                   - qux
                   - fox
             END
    data["foo"]["bar"]["baz"][1].as_s # => "fox"

`YAML.parse` can read from an `IO` directly (such as a file) which saves allocating a string:

    require "yaml"

    yaml = File.open("path/to/file.yml") do |file|
      YAML.parse(file)
    end

###
Parsing with `from_yaml`

A type `T` can be deserialized from YAML by invoking `T.from_yaml(string_or_io)`. For this to work, `T` must implement `new(ctx : YAML::PullParser, node : YAML::Nodes::Node)` and decode a value from the given node, using ctx to store and retrieve anchored values (see `YAML::PullParser` for an explanation of this).

Crystal primitive types, `Time`, `Bytes` and `Union` implement this method. `YAML.mapping` can be used to implement this method for user types.

###
Dumping with `YAML.dump` or `#to_yaml`

`YAML.dump` generates the YAML representation for an object. An `IO` can be passed and it will be written there, otherwise it will be returned as a string. Similarly, `#to_yaml` (with or without an `IO`) on any object does the same.

For this to work, the type given to `YAML.dump` must implement `to_yaml(builder : YAML::Nodes::Builder`).

Crystal primitive types, `Time` and `Bytes` implement this method. `YAML.mapping` can be used to implement this method for user types.

    yaml = YAML.dump({hello: "world"})                               # => "---\nhello: world\n"
    File.open("foo.yml", "w") { |f| YAML.dump({hello: "world"}, f) } # writes it to the file
    # or:
    yaml = {hello: "world"}.to_yaml                               # => "---\nhello: world\n"
    File.open("foo.yml", "w") { |f| {hello: "world"}.to_yaml(f) } # writes it to the file

##
Defined in:

##
Class Method Summary

- .build(io : IO, &)

  Writes YAML into the given `IO`.

- .build(&)

  Returns the resulting String of writing YAML to the yielded `YAML::Builder`.

- .dump(object, io : IO)

  Serializes an object to YAML, writing it to io.

- .dump(object) : String

  Serializes an object to YAML, returning it as a `String`.

- .libyaml_version : SemanticVersion

  Returns the used version of `libyaml`.

- .parse(data : String | IO) : Any

  Deserializes a YAML document according to the core schema.

- .parse_all(data : String) : Array(Any)

  Deserializes multiple YAML documents according to the core schema.

##
Macro Summary

- mapping(_properties_, strict = false)

  The `YAML.mapping` macro defines how an object is mapped to YAML.

- mapping(**_properties_)

  This is a convenience method to allow invoking `YAML.mapping` with named arguments instead of with a hash/named-tuple literal.

##
Class Method Detail

def self.build(io : IO, &) #

Writes YAML into the given `IO`. A `YAML::Builder` is yielded to the block.

def self.build(&) #

Returns the resulting String of writing YAML to the yielded `YAML::Builder`.

    require "yaml"

    string = YAML.build do |yaml|
      yaml.mapping do
        yaml.scalar "foo"
        yaml.sequence do
          yaml.scalar 1
          yaml.scalar 2
        end
      end
    end
    string # => "---\nfoo:\n- 1\n- 2\n"

def self.dump(object, io : IO) #

Serializes an object to YAML, writing it to io.

def self.dump(object) : String #

Serializes an object to YAML, returning it as a `String`.

def self.libyaml_version : SemanticVersion #

Returns the used version of `libyaml`.

def self.parse(data : String | IO) : Any #

Deserializes a YAML document according to the core schema.

    # ./foo.yml
    data:
      string: "foobar"
      array:
        - John
        - Sarah
      hash: {key: value}
      paragraph: |
        foo
        bar

    require "yaml"

    YAML.parse(File.read("./foo.yml"))
    # => {
    # => "data" => {
    # => "string" => "foobar",
    # => "array" => ["John", "Sarah"],
    # => "hash" => {"key" => "value"},
    # => "paragraph" => "foo\nbar\n"
    # => }

def self.parse_all(data : String) : Array(Any) #

Deserializes multiple YAML documents according to the core schema.

    # ./foo.yml
    foo: bar
    ---
    hello: world

    require "yaml"

    YAML.parse_all(File.read("./foo.yml"))
    # => [{"foo" => "bar"}, {"hello" => "world"}]

##
Macro Detail

macro mapping(_properties_, strict = false) #

The `YAML.mapping` macro defines how an object is mapped to YAML.

It takes named arguments, a named tuple literal or a hash literal as argument, in which attributes and types are defined. Once defined, `Object#from_yaml` populates properties of the class from the YAML document.

    require "yaml"

    class Employee
      YAML.mapping(
        title: String,
        name: String,
      )
    end

    employee = Employee.from_yaml("title: Manager\nname: John")
    employee.title # => "Manager"
    employee.name  # => "John"

    employee.name = "Jenny"
    employee.name # => "Jenny"

Attributes not mapped with `YAML.mapping` are not defined as properties. Also, missing attributes raise a `ParseException`.

    employee = Employee.from_yaml("title: Manager\nname: John\nage: 30")
    employee.age # undefined method 'age'. (compile error)

    Employee.from_yaml("title: Manager") # raises YAML::ParseException

You can also define attributes for each property.

    class Employer
      YAML.mapping(
        title: String,
        name: {
          type:    String,
          nilable: true,
          key:     "firstname",
        },
      )
    end

Available attributes:

- type (required) defines its type. In the example above, title: String is a shortcut to title: {type: String}.
- nilable defines if a property can be a `Nil`. Passing `T?` as a type has the same effect.
- default: value to use if the property is missing in the YAML document, or if it's `null` and `nilable` was not set to `true`. If the default value creates a new instance of an object (for example `[1, 2, 3]` or `SomeObject.new`), a different instance will be used each time a YAML document is parsed.
- key defines which key to read from a YAML document. It defaults to the name of the property.
- converter takes an alternate type for parsing. It requires a `#from_yaml` method in that class, and returns an instance of the given type. Examples of converters are `Time::Format` and `Time::EpochConverter` for `Time`.
- setter: if `true`, will generate a setter for the variable, `true` by default
- getter: if `true`, will generate a getter for the variable, `true` by default
- presence: if `true`, a `{{key}}_present?` method will be generated when the key was present (even if it has a `null` value), `false` by default

This macro by default defines getters and setters for each variable (this can be overrided with setter and getter). The mapping doesn't define a constructor accepting these variables as arguments, but you can provide an overload.

The macro basically defines a constructor accepting a `YAML::PullParser` that reads from it and initializes this type's instance variables.

This macro also declares instance variables of the types given in the mapping.

macro mapping(**_properties_) #

This is a convenience method to allow invoking `YAML.mapping` with named arguments instead of with a hash/named-tuple literal.