Serialization

There are multiple approaches to implement model serialization to a required format.

General

Jennifer defines some hidden instance attributes in defined models for own use. Sometimes we would like to operate with a class where we have full access to all defined attributes/methods. For this case the easiest way is to use JenniferTwin lib. Using it you can dump Jennifer model instance to a separate object that is totally under your control. One of the cases when this approach may come in handy when ther is a need to use attribute annotations, like MessagePack::Serializable or JSON::Serializable.

class User < Jennifer::Model::Base
  mapping(
    id: Primary64,
    name: String?,
    age: Int32?,
    password_hash: String?
  )
end

class UserTwin
  include JenniferTwin
  include JSON::Serializable

  map_fields(User, {
    name: {key: :full_name},
    password_hash: {ignore: true}
  })

  setter full_name

  @[JSON::Field(emit_null: false)]
  @age : Int32?
end

user = User.all.first # <User:0x000000000010 id: 1, name: "User 8", age: nil, password_hash: "<hash>">
user_twin = UserTwin.new(user) # <UserTwin:0x000000000020 @id=1, @full_name="User 8", @age=nil>
user_twin.to_json # => %({"id":1,"full_name":"User 8","age":null})

Also you can easily convert such twin back to model

user_twin.full_name = "New Name"
user_twin.to_modal # <User:0x000000000030 id: nil, name: "New Name", age: nil, password_hash: nil>

JSON

For JSON serialization there are 3 options (one of which is described above).

#to_json

Out of the box Jennifer provides #to_json method at model and query levels. They allows you to serialize specific records all together with any additional custom properties.

user = User.all.first # <User:0x000000000010 id: 1, name: "User 8", age: nil, password_hash: "<hash>">
user.to_json # => {"id":1,"name":"User 8","age":null}

To specify exact subset of fields that should be serialized use only argument

user.to_json(only: %w[id name]) # => {"id":1,"name":"User 8"}
# or just
user.to_json(%w[id name])

It is possible to specify exception list of fields by except argument:

user.to_json(except: %w[id]) # => {"name":"User 8","age":null}

To extend serialized object you can pass a block:

user.to_json(only: %w[id name]) do |json|
  json.field "custom", "value"
end # => {"id":1,"name":"User 8","custom":"value}

To serialize collection retrieved from the database call #to_json method of Jennifer::Query directly. It accepts the same arguments as described above.

User.all.where { _name.like("%ohn%) }.to_json(except: %w[age]) do |json, record|
  json.field "custom", record.age
end # => [{"id":1,"name":"John","custom":23}]

Serializer

As an alternate you can use Serializer library dedicated to this purpose - serialize Jennifer object relations. Apart from simple attribute mapping/renaming you can also specify all relationships (has_many/belongs_to/has_one), override methods, dynamically specify which attributes/relations should be serialized.

class CommentSerializer < Serializer::Base(Comment)
  attribute :text

  belongs_to :post, PostSerializer
end

class PostSerializer < Serializer::Base(Post)
  attribute :title, :Title, if: :test_title
  attribute :body
  attribute :category

  has_many :comments, CommentSerializer

  def test_title(object, options)
    options.nil? || !options[:test]?
  end

  def category
    12
  end
end

ModelSerializer.new(Post.last).serialize(
  except: [:category],
  includes: {
    :comments => [:post], # you can specify relations any level deep
  },
  meta: { :page => 0 } # and add meta data
)

More descriptions you can find on it’s github page.