JSerializer is a JSON Serializer for Ruby objects. It is designed to be a drop-in replacement of Active Model Serializer (target version: 0.8) with better performance.
JSerializer does not rely on Rails or Active Model or Active Support, which makes it easier to be integrated into general Ruby projects.
Add this line to your application's Gemfile:
gem 'jserializer'
And then execute:
$ bundle
Or install it yourself as:
$ gem install jserializer
Person = Struct.new(:id, :first_name, :last_name, :age, :gender, :country)
class PersonSerializer < Jserializer::Base
root :user
attributes :full_name, :age, :gender
attribute :country, key: :country_code
def full_name
"#{object.first_name} #{object.last_name}"
end
def gender
object.gender == 'm' ? 'Male' : 'Female'
end
def include_age?
object.age >= 18
end
end
person = Person.new(1, 'John', 'Doe', 16, 'm', 'US')
serializer = PersonSerializer.new(person)
# generates a Hash without root key
# => {:full_name=>"John Doe", :gender=>"Male", :country_code=>"US"}
serializer.serializable_hash
# generates a Hash with root key
# => {:user=>{:full_name=>"John Doe", :gender=>"Male", :country_code=>"US"}}
serializer.as_json
# generates JSON => {"user":{"full_name":"John Doe","gender":"Male","country_code":"US"}}
serializer.to_json
persons = 2.times.map{|i| Person.new(i, 'Person', "#{i}", 17 + i, 'm', 'US') }
serializer = PersonSerializer.new(persons, is_collection: true)
serializer.to_json
You will get:
{
"user":[
{
"full_name":"Person 0",
"gender":"Male",
"country_code":"US"
},
{
"full_name":"Person 1",
"age":18,
"gender":"Male",
"country_code":"US"
}
]
}
Our to_json
method uses standard JSON module to generate JSON string. There are many JSON encode backend there, and they offer different customization options. Besides, you can use MultiJson to switch between different backends.
You are welcome to bring your own solution here. To do that, simply override the to_json
method
class ApplicationSerializer < Jserializer::Base
def to_json(*)
# use ActiveSupport in Rails as a delegator
ActiveSupport::JSON.encode(as_json)
# use oj:
# Oj.dump(as_json, mode: :compat, use_to_json: true)
end
end
Then the rest of your serializers can inherit from ApplicationSerializer
and start to use your preferred encoder.
Method | Options | Description |
---|---|---|
root | N/A | Set the root key of the generated JSON |
attributes | N/A | Define a list of fields separated by , to be exposed from a Ruby object |
attribute | :key - The name in the JSON output | Similar to attributes but for one field |
has_many | :serializer :key :embed :embed_key |
Include a collection of objects with has many association |
has_one | Same as has_many | Include a object with has one association |
embed | :ids :objects |
Determine if only include IDs of the associations |
This example shows you where to apply the above methods
class PostSerializer < Jserializer::Base
root :article
embed :ids
attributes :id, :title, :content
attribute :writer, key: :written_by
has_many :comments, serializer: CommentSerializer, embed: :objects
has_one :author, serializer: AuthorSerializer, embed_key: :id
end
For associations, Jserializer uses the following ways to retrieve data:
Type | Method | Example |
---|---|---|
has_many | collection_singular_ids |
posts => post_ids |
has_one | association.id |
account => account.id |
Options | Description |
---|---|
root | Set the root key of the generated JSON, set it to false to disable |
meta | Meta information to be included in the JSON output |
meta_key | The key name of the meta information, the default is :meta |
is_collection | Whether the given object is a collection or single object |
only | An array of attributes to be included in the JSON output |
except | An array of attributes to be excluded in the JSON output |
current_user | Use for determine the authorization scope |
PostSerializer.new(posts,
root: :post,
meta: { page: 1, total: 100},
is_collection: true,
only: [:title, :content])
You can enable/disable root when initializing a serializer instance:
PostSerializer.new(post, root: false)
Or when calling as_json
method:
# here the root option only accept a boolean value
# you cannot rename root at this point
PostSerializer.new(post).as_json(root: false)
You can always get the Hash representation without root
and meta
information by calling serializable_hash
PostSerializer.new(post).serializable_hash
The active_serializer_model
gem includes the ArraySerializer
class to handle collections. There are a lot of magics happening underneath when you pass a collection object into render json: @xxx
, to allow ArraySerializer
gets triggered automatically.
Unlike active_serializer_model
, there is no separate serializer class for array. To serialize a collection, you need to set is_collection: true
when initializing a new serializer
serializer = PostSerializer.new(posts, is_collection: true)
serializer.serializable_hash # or serializer.as_json to include root
You can also call serializable_collection
method directly which will ignore the is_collection
option
serializer = PostSerializer.new(posts)
serializer.serializable_collection
Currently, this gem is not compatible with active_serializer_model
if you:
- have
include_xxx?
as private method - override the instance method
attributes
- override any internal method
_xxx
(e.g._serializable_array
) - expect serializer to automatically include a root for you
- expect serializer figures out if the object is a collection automatically
Since we try to reuse serializer instances to avoid unnecessary object creations, make sure there is no things like ||=
in the serializer class. Or you can override reset
method to clean things out
class MySerializer < Jserializer::Base
... ...
def reset(object)
@my_cached_stuff = nil
... ...
super
end
This gem will try to find and use the serializer class defined by active_model_serializer
method in a model, if you don't specify serializer
explicitly
class Post < ActiveRecord::Base
def active_model_serializer
MyPostSerializer
end
end
Active Model Serializer overrides render :json
in ActionController::Serialization, which is convenient. But it touches Rails internal methods which could bring compatibility issues when upgrading Rails.
This gem does not provide such feature, but you can easily achieve it in application layer, for example, create a wrapper method for render
:
class ApplicationController < ActionController::Base
# ... ...
def render_json(resource, options = {})
if options.key?(:serializer)
serializer = options.delete(:serializer)
elsif options.key?(:each_serializer)
serializer = options.delete(:each_serializer)
options[:is_collection] = true
end
if !serializer && resource.respond_to?(:active_model_serializer)
serializer = resource.active_model_serializer
end
if serializer
options[:scope] = current_user
options[:json] = serializer.new(resource, options)
else
options[:json] = resource
end
render options
end
end
Then you can use this render_json
method whenever you need to call render json: resource ...
in your controllers. And this is probably a good way to migrate gradually.
This gem does not plan to implement the cache feature.
Bug reports and pull requests are welcome on GitHub at https://github.com/distil/jserializer.