YAML::Serializable - Crystal 1.20.1

# module YAML::Serializable

##
Overview

The `YAML::Serializable` module automatically generates methods for YAML serialization when included.

###
Example

    require "yaml"

    class Location
      include YAML::Serializable

      @[YAML::Field(key: "lat")]
      property latitude : Float64

      @[YAML::Field(key: "lng")]
      property longitude : Float64
    end

    class House
      include YAML::Serializable
      property address : String
      property location : Location?
    end

    house = House.from_yaml(%({"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}))
    house.address  # => "Crystal Road 1234"
    house.location # => #<Location:0x10cd93d80 @latitude=12.3, @longitude=34.5>
    house.to_yaml  # => "---\naddress: Crystal Road 1234\nlocation:\n  lat: 12.3\n  lng: 34.5\n"

    houses = Array(House).from_yaml("---\n- address: Crystal Road 1234\n  location:\n    lat: 12.3\n    lng: 34.5\n")
    houses.size    # => 1
    houses.to_yaml # => "---\n- address: Crystal Road 1234\n  location:\n    lat: 12.3\n    lng: 34.5\n"

###
Usage

Including `YAML::Serializable` will create `#to_yaml` and `self.from_yaml` methods on the current class, and a constructor which takes a `YAML::PullParser`. By default, these methods serialize into a yaml object containing the value of every instance variable, the keys being the instance variable name. Most primitives and collections supported as instance variable values (string, integer, array, hash, etc.), along with objects which define to_yaml and a constructor taking a `YAML::PullParser`. Union types are also supported, including unions with nil. If multiple types in a union parse correctly, it is undefined which one will be chosen.

To change how individual instance variables are parsed and serialized, the annotation `YAML::Field` can be placed on the instance variable. Annotating property, getter and setter macros is also allowed.

    require "yaml"

    class A
      include YAML::Serializable

      @[YAML::Field(key: "my_key", emit_null: true)]
      getter a : Int32?
    end

`YAML::Field` properties:

- ignore: if `true` skip this field in serialization and deserialization (by default false)
- ignore_serialize: If truthy, skip this field in serialization (default: `false`). The value can be any Crystal expression and is evaluated at runtime.
- ignore_deserialize: if `true` skip this field in deserialization (by default false)
- key: the value of the key in the yaml object (by default the name of the instance variable)
- converter: specify an alternate type for parsing and generation. The converter must define `from_yaml(YAML::ParseContext, YAML::Nodes::Node)` and `to_yaml(value, YAML::Nodes::Builder)`. Examples of converters are a `Time::Format` instance and `Time::EpochConverter` for `Time`.
- presence: if `true`, a `@{{key}}_present` instance variable will be generated when the key was present (even if it has a `null` value), `false` by default
- emit_null: if `true`, emits a `null` value for nilable property (by default nulls are not emitted)

Deserialization also respects default values of variables:

    require "yaml"

    struct A
      include YAML::Serializable
      @a : Int32
      @b : Float64 = 1.0
    end

    A.from_yaml("---\na: 1\n") # => A(@a=1, @b=1.0)

NOTE `YAML::Serializable` defines an internal constructor on any including type, which means the default constructor (`def initialize; end`) is absent unless explicitly defined by the user, even when all instance variables have a default initializer.

###
Extensions: `YAML::Serializable::Strict` and `YAML::Serializable::Unmapped`.

If the `YAML::Serializable::Strict` module is included, unknown properties in the YAML document will raise a parse exception. By default the unknown properties are silently ignored. If the `YAML::Serializable::Unmapped` module is included, unknown properties in the YAML document will be stored in a `Hash(String, YAML::Any)`. On serialization, any keys inside yaml_unmapped will be serialized appended to the current yaml object.

    require "yaml"

    struct A
      include YAML::Serializable
      include YAML::Serializable::Unmapped
      @a : Int32
    end

    a = A.from_yaml("---\na: 1\nb: 2\n") # => A(@yaml_unmapped={"b" => 2}, @a=1)
    a.yaml_unmapped["b"].raw.class       # => Int64
    a.to_yaml                            # => "---\na: 1\nb: 2\n"

###
Class annotation `YAML::Serializable::Options`

supported properties:

- emit_nulls: if `true`, emits a `null` value for all nilable properties (by default nulls are not emitted)

    require "yaml"

    @[YAML::Serializable::Options(emit_nulls: true)]
    class A
      include YAML::Serializable
      @a : Int32?
    end

###
Discriminator field

A very common YAML serialization strategy for handling different objects under a same hierarchy is to use a discriminator field. For example in GeoJSON each object has a "type" field, and the rest of the fields, and their meaning, depend on its value.

You can use `YAML::Serializable.use_yaml_discriminator` for this use case.

###
`after_initialize` method

`#after_initialize` is a method that runs after an instance is deserialized from YAML. It can be used as a hook to post-process the initialized object.

Example:

    require "yaml"

    class Person
      include YAML::Serializable
      getter name : String

      def after_initialize
        @name = @name.upcase
      end
    end

    person = Person.from_yaml "---\nname: Jane\n"
    person.name # => "JANE"

##
Defined in:

yaml/serialization.cr

##
Macro Summary

- use_yaml_discriminator(field, mapping)

  Tells this class to decode YAML by using a field as a discriminator.

##
Instance Method Summary

- #to_yaml(yaml : YAML::Nodes::Builder)

##
Macro Detail

macro use_yaml_discriminator(field, mapping) #

Tells this class to decode YAML by using a field as a discriminator.

- field must be the field name to use as a discriminator
- mapping must be a hash or named tuple where each key-value pair maps a discriminator value to a class to deserialize

For example:

    require "yaml"

    abstract class Shape
      include YAML::Serializable

      use_yaml_discriminator "type", {point: Point, circle: Circle}

      property type : String
    end

    class Point < Shape
      property x : Int32
      property y : Int32
    end

    class Circle < Shape
      property x : Int32
      property y : Int32
      property radius : Int32
    end

    Shape.from_yaml(%(
      type: point
      x: 1
      y: 2
    )) # => #<Point:0x10373ae20 @type="point", @x=1, @y=2>

    Shape.from_yaml(%(
      type: circle
      x: 1
      y: 2
      radius: 3
    )) # => #<Circle:0x106a4cea0 @type="circle", @x=1, @y=2, @radius=3>

##
Instance Method Detail

def to_yaml(yaml : YAML::Nodes::Builder) #