Serializer
Serializer is a simple JSON serialization library for your object structure. Unlike core JSON
module's functionality this library only covers serializing objects to JSON without parsing data back. At the same time it provides some free space for maneuvers, precise and flexible configuration WHAT, HOW and WHEN should be rendered.
Serializer::Base
only ~11% slower than JSON::Serializable
Serializer 646.00k ( 1.55µs) (± 2.52%) 2.77kB/op 1.11× slower
JSON::Serializable 719.74k ( 1.39µs) (± 2.39%) 1.3kB/op fastest
and at the same time provides next functionality:
- conditional rendering at schema definition stage
- excluding specific fields at invocation stage
- separation fields from relations
- deep relation specification (to be rendered) at invocation stage
- inheritance
- optional meta data (can be specified at both definition and invocation stages).
Installation
-
Add the dependency to your
shard.yml
:dependencies: serializer: github: imdrasil/serializer
-
Run
shards install
Usage
Let's assume we have next resources relationship
class Parent
property name, title,
children : Array(Child),
friends : Array(Child)
def initialize(@name = "test", @title = "asd", @children = [] of Child, @friends = [] of Child)
end
end
class Child
property age : Int32, dipper : Child?, address : Address?
def initialize(@age, @dipper = nil, @address = nil)
end
def some_sub_relation; end
end
class Address
property street
def initialize(@street = "some street")
end
end
To be able to serialize data we need to define serializers for each resource:
class AddressSerializer < Serializer::Base(Address)
attributes :street
end
class ChildSerializer < Serializer::Base(Child)
attribute :age
has_one :some_sub_relation, ChildSerializer
has_one :address, AddressSerializer
has_one :dipper, ChildSerializer
end
class ModelSerializer < Serializer::Base(Model)
attribute :name
attribute :title, :Title, if: :test_title
attribute :own_field
has_many :children, ChildSerializer
has_many :friends, ChildSerializer
def test_title(object, options)
options.nil? || !options[:test]?
end
def own_field
12
end
end
Attributes
To specify what should be serialized attributes
and attribute
macros are used. attributes
allows to pass a list of attribute names which maps one-to-one with JSON keys
class PostSerializer
attributes :title, body
end
Above serializer will produce next output {"title": "Some title", "body": "Post body"}
. You can precisely configure every field using attribute
macro. It allows to specify key
name to be used in JSON and if
predicate method name to be used to check whether field should be serialized.
class ModelSerializer < Serializer::Base(Model)
attribute :title, :Title, if: :test_title
def test_title(object, options)
options.nil? || !options[:test]?
end
end
Above serializer will produce next output {"Title": "Some title"}
if serializer has got options without test
set to true
.
If serializer has a method with the same name as specified field - it is used.
class ModelSerializer < Serializer::Base(Model)
attribute :name
def name
"StaticName"
end
end
Relations
If resource has underlying resources to serialize they can be specified with has_one
, belongs_to
and has_many
macro methods that describes relation type between them (one-to-one, one-to-any and one-to-many).
class ModelSerializer < Serializer::Base(Model)
has_many :friends, ChildSerializer
end
They also accepts key
option. There is no if
support because associations by default isn't rendered.
Meta
Resource meta data can be defined at it's level - overriding .meta
method.
class ModelSerializer < Serializer::Base(Model)
def self.meta(options)
{
:page => options[:page]
}
end
end
Method return value should be Hash(Symbol, JSON::Any::Type | Int32)
. Also any additional meta attributes may be defined at serialization moment (calling #serialize
method).
Inheritance
If you have complicated domain object relation structure - you can easily present serialization logic using inheritance:
class ModelSerializer < Serializer::Base(Model)
attribute :name
end
class InheritedSerializer < ModelSerializer
attribute :inherited_field
def inherited_field
1.23
end
end
Rendering
To render resource create an instance of required serializer and call #serialize
:
ModelSerializer.new(model).serialize
It accepts several optional arguments:
except
- array of fields that should not be serialized;includes
- relations that should be included into serialized string;opts
- options that will be passed to if predicate methods and.meta
;meta
- meta attributes to be added under"meta"
key at root level; it is merged into default meta attributes returned by.meta
.
ModelSerializer.new(model).serialize(
except: [:own_field],
includes: {
:children => [:some_sub_relation],
:friends => { :address => nil, :dipper => [:some_sub_relation] }
},
meta: { :page => 0 }
)
includes
should be array or hash (any levels deep) which elements presents relation names to be serialized. nil
value may be used in hashes as a value to define that nothing additional should be serialized for a relation named by corresponding key.
Example above results in:
{
"data":{
"name":"test",
"Title":"asd",
"children":[],
"friends":[
{
"age":60,
"address":{
"street":"some street"
},
"dipper":{
"age":20,
"some_sub_relation":null
}
}
]
},
"meta":{
"page":0
}
}
This is pretty JSON version - actual result contains no spaces and newlines.
Root key
Serialized JSON root level includes data
key (and optional meta
key). It can be renamed to anything by defining .root_key
class ModelSerializer < Serializer::Base(Model)
def self.root_key
"model"
end
attribute :name
end
For API details see documentation.
Contributing
- Fork it (https://github.com/imdrasil/serializer/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Contributors
- Roman Kalnytskyi - creator and maintainer