module NASON::Serializable

Overview

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

Example

require "nason"

class Location
  include NASON::Serializable

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

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

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

house = House.from_nason(%({"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_nason # => %({"address":"Crystal Road 1234","location":{"lat":12.3,"lng":34.5}})

houses = Array(House).from_nason(%([{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}]))
houses.size     # => 1
houses.to_nason # => %([{"address":"Crystal Road 1234","location":{"lat":12.3,"lng":34.5}}])

Usage

Including NASON::Serializable will create #to_nason and self.from_nason methods on the current class, and a constructor which takes a NASON::PullParser. By default, these methods serialize into a json 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_nason and a constructor taking a NASON::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 NASON::Field can be placed on the instance variable. Annotating property, getter and setter macros is also allowed. You need add type Null to the field if the field is allowed to have null value. To set null value to the field, just assign constant NULL to it.

require "nason"

class A
  include NASON::Serializable

  @[NASON::Field(key: "my_key")]
  property a : Int32? | Null
end

obj = A.from_nason(%({"my_key":null})) # => A(@a=null)
obj.a                                  # => null
obj.a == NULL                          # => true
obj.to_nason                           # => "{\"my_key\":null}"

NASON::Field properties:

Deserialization also respects default values of variables:

require "nason"

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

A.from_nason(%<{"a":1}>) # => A(@a=1, @b=1.0)

Extensions: NASON::Serializable::Strict and NASON::Serializable::Unmapped.

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

require "nason"

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

a = A.from_nason(%({"a":1,"b":2})) # => A(@json_unmapped={"b" => 2_i64}, @a=1)
a.to_nason                         # => {"a":1,"b":2}

Discriminator field

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

You can use NASON::Serializable.use_json_discriminator for this use case.

Defined in:

nason/serialization.cr

Constructors

Instance Method Summary

Macro Summary

Constructor Detail

def self.new(*, __pull_for_json_serializable pull : NASON::PullParser) #

[View source]

Instance Method Detail

def to_nason(json : NASON::Builder) #

[View source]

Macro Detail

macro use_json_discriminator(field, mapping) #

Tells this class to decode NASON 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 "nason"

abstract class Shape
  include NASON::Serializable

  use_json_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_nason(%({"type": "point", "x": 1, "y": 2}))               # => #<Point:0x10373ae20 @type="point", @x=1, @y=2>
Shape.from_nason(%({"type": "circle", "x": 1, "y": 2, "radius": 3})) # => #<Circle:0x106a4cea0 @type="circle", @x=1, @y=2, @radius=3>

[View source]