module Hash::Serializable

Overview

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

Example

require "hash_serializable"

class Note
  include Hash::Serializable

  property message : String = "DEFAULT"
end

class Location
  include Hash::Serializable

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

  @[Hash::Field(key: "lon")]
  property longitude : Float64

  property note : Note
end

class House
  include Hash::Serializable

  property address : String
  property location : Location?
  property note : Note
end

arg = {
  "note" => {
    "message" => "Nice Address",
  },
  "address"  => "Crystal Road 1234",
  "location" => {
    "lat"  => 12.3,
    "lon"  => 34.5,
    "note" => {
      "message" => "hmmmm",
    },
  },
}
house = House.from_hash(arg)

house.is_a?(House).should be_true
house.address.should eq "Crystal Road 1234"
house.location.is_a?(Location).should be_true
house.location.not_nil!.latitude.should eq 12.3
house.location.not_nil!.longitude.should eq 34.5
house.note.message.should eq "Nice Address"
house.location.not_nil!.note.message.should eq "hmmmm"
house.to_hash.should eq arg

### Usage

Including `Hash::Serializable` will create `#to_hash` and `self.from_hash` methods
on the current class, and a constructor which takes a Hash. By default, `self.from_hash`
will deserialize a Hash into an instance of the object that it is passed to, according
to the definition of the class, and `#to_hash` will serialize the class into a Hash
containing the value of every instance variable, the keys being the instance variable
names.

It will descend through a nested class structure, where variables in one class
point to objects that, in turn, have instance variables. It should also deal correctly
with type unions.

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

require "hash_serializable"

struct A include Hash::Serializable

@[Hash::Field(key: "my_key")] getter a : Int32? end


`Hash::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)
* **cast**: takes either a proc, or a method name; if proc, the hash value will be passed to the proc, and the return value will be used as the value of the instance variable; if method, the method will be called *on* the hash value, and the return value used for the instance variable value
* **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; this does not declare the `@{{key}}_present` variable for you, so you will be responsible for ensuring that a Bool variable is declared

Deserialization respects default values of variables.

### Extensions: `Hash::Serializable::Strict` and `Hash::Serializable::Unmapped`

If the `Hash::Serializable::Strict` module is included, unknown properties in the Hash
document will raise an exception. By default the unknown properties are silently ignored.

If the `Hash::Serializable::Unmapped` module is included, unknown properties in the Hash
will be stored in a hash with an appropriate type signature. On serialization, any keys inside json_unmapped
will be serialized into the hash, as well.

require "hash_serializable"

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

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


## Casting

*Hash::Serializable* can automatically convert values from one type to another. For example, if one has a *RequestParams* object that serialized the query parameters in an HTTP request, and one wanted to define a *user_id* field that was an integer, one might do something like this:

  ```crystal
  struct RequestParams
    use Hash::Serializable

    @[Hash::Field(cast: :to_i)]
    getter user_id : Int32
  end

  params = RequestParams.new({"user_id" => "123"})
  pp params # >    #<RequestParams:0x7f2653e2f080 @user_id=123 >

One can also provide a proc to do the value conversion:

struct MyObj
  use Hash::Serializable

  @[Hash::Field(
    key: "number",
    cast: ->(x : String | Int::Signed | Int::Unsigned | Float::Primitive) do
      BigInt.new(x) ** 2
    end)]
  getter square : BigInt
end

obj = MyObj.new({"number" => 12345678901234567890})
pp obj # >    #<MyObj:0x7fc5adbe5e80 @square=152415787532388367501905199875019052100>

The library does not yet support having procs or methods which will cast back to the original type.

Defined in:

hash_serializable.cr

Constant Summary

VERSION = "0.2.0"

Constructors

Instance Method Summary

Constructor Detail

def self.new(hash : U) forall U #

[View source]

Instance Method Detail

def to_hash #

[View source]