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:
- ignore: if
true
skip this field in serialization and deserialization (by default false) - ignore_serialize: if
true
skip this field in serialization (by default false) - ignore_deserialize: if
true
skip this field in deserialization (by default false) - key: the value of the key in the json object (by default the name of the instance variable)
- root: assume the value is inside a NASON object with a given key (see
Object.from_nason(string_or_io, root)
) - converter: specify an alternate type for parsing and generation. The converter must define
from_nason(NASON::PullParser)
andto_nason(value, NASON::Builder)
. Examples of converters are aTime::Format
instance andTime::EpochConverter
forTime
.
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.crConstructors
Instance Method Summary
Macro Summary
-
use_json_discriminator(field, mapping)
Tells this class to decode NASON by using a field as a discriminator.
Constructor Detail
Instance Method Detail
Macro Detail
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>