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:

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)

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:

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

Constructors

Instance Method Summary

Macro Summary

Constructor Detail

def self.new(*, __context_for_yaml_serializable ctx : YAML::ParseContext, __node_for_yaml_serializable node : YAML::Nodes::Node) #

[View source]

Instance Method Detail

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

[View source]

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>

[View source]