Alba is the fastest JSON serializer for Ruby, JRuby, and TruffleRuby.
Alba uses GitHub Discussions to openly discuss the project.
If you've already used Alba, please consider posting your thoughts and feelings on Feedback. The fact that you enjoy using Alba gives me energy to keep developing Alba!
If you have feature requests or interesting ideas, join us with Ideas. Let's make Alba even better, together!
Because it's fast, flexible and well-maintained!
Alba is faster than most of the alternatives. We have a benchmark.
Alba provides a small set of DSL to define your serialization logic. It also provides methods you can override to alter and filter serialized hash so that you have full control over the result.
Alba is well-maintained and adds features quickly. Coverage Status and CodeClimate Maintainability show the code base is quite healthy.
Add this line to your application's Gemfile:
gem 'alba'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install alba
Alba supports CRuby 2.5 and higher and latest JRuby and TruffleRuby.
You can find the documentation on RubyDoc.
- Resource-based serialization
- Arbitrary attribute definition
- One and many association with the ability to define them inline
- Adding condition and filter to association
- Parameters can be injected and used in attributes and associations
- Conditional attributes and associations
- Selectable backend
- Key transformation
- Root key inference
- Error handling
- Resource name inflection based on association name
- Circular associations control
- No runtime dependencies
- Sorting keys
- Class level support of parameters
- Supporting all existing JSON encoder/decoder
- Cache
- JSON:API support
- And many others
Alba's configuration is fairly simple.
Backend is the actual part serializing an object into JSON. Alba supports these backends.
- Oj, the fastest. Gem installation required.
- active_support, mostly for Rails. Gem installation required.
- default or json, with no external dependencies.
You can set a backend like this:
Alba.backend = :oj
You can enable inference feature using enable_inference!
method.
Alba.enable_inference!
You must install ActiveSupport
to enable inference.
You can configure error handling with on_error
method.
Alba.on_error :ignore
For the details, see Error handling section
class User
attr_accessor :id, :name, :email, :created_at, :updated_at
def initialize(id, name, email)
@id = id
@name = name
@email = email
@created_at = Time.now
@updated_at = Time.now
end
end
class UserResource
include Alba::Resource
key :user
attributes :id, :name
attribute :name_with_email do |resource|
"#{resource.name}: #{resource.email}"
end
end
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
UserResource.new(user).serialize
# => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
class User
attr_reader :id, :created_at, :updated_at
attr_accessor :articles
def initialize(id)
@id = id
@created_at = Time.now
@updated_at = Time.now
@articles = []
end
end
class Article
attr_accessor :user_id, :title, :body
def initialize(user_id, title, body)
@user_id = user_id
@title = title
@body = body
end
end
class ArticleResource
include Alba::Resource
attributes :title
end
class UserResource
include Alba::Resource
attributes :id
many :articles, resource: ArticleResource
end
user = User.new(1)
article1 = Article.new(1, 'Hello World!', 'Hello World!!!')
user.articles << article1
article2 = Article.new(2, 'Super nice', 'Really nice!')
user.articles << article2
UserResource.new(user).serialize
# => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
Alba.serialize
method is a shortcut to define everything inline.
Alba.serialize(user, key: :foo) do
attributes :id
many :articles do
attributes :title, :body
end
end
# => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
You can exclude
or ignore
certain attributes using ignoring
.
class Foo
attr_accessor :id, :name, :body
def initialize(id, name, body)
@id = id
@name = name
@body = body
end
end
class GenericFooResource
include Alba::Resource
attributes :id, :name, :body
end
class RestrictedFooResouce < GenericFooResource
ignoring :id, :body
end
RestrictedFooResouce.new(foo).serialize
# => '{"name":"my foo"}'
end
** Note: You need to install active_support
gem to use transform_keys
DSL.
With active_support
installed, you can transform attribute keys.
class User
attr_reader :id, :first_name, :last_name
def initialize(id, first_name, last_name)
@id = id
@first_name = first_name
@last_name = last_name
end
end
class UserResource
include Alba::Resource
attributes :id, :first_name, :last_name
transform_keys :lower_camel
end
user = User.new(1, 'Masafumi', 'Okura')
UserResourceCamel.new(user).serialize
# => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
Supported transformation types are :camel, :lower_camel and :dash.
You can filter attributes by overriding Alba::Resource#converter
method, but it's a bit tricky.
class User
attr_accessor :id, :name, :email, :created_at, :updated_at
def initialize(id, name, email)
@id = id
@name = name
@email = email
end
end
class UserResource
include Alba::Resource
attributes :id, :name, :email
private
# Here using `Proc#>>` method to compose a proc from `super`
def converter
super >> proc { |hash| hash.compact }
end
end
user = User.new(1, nil, nil)
UserResource.new(user).serialize # => '{"id":1}'
The key part is the use of Proc#>>
since Alba::Resource#converter
returns a Proc
which contains the basic logic and it's impossible to change its behavior by just overriding the method.
It's not recommended to swap the whole conversion logic. It's recommended to always call super
when you override converter
.
Filtering attributes with overriding convert
works well for simple cases. However, It's cumbersome when we want to filter various attributes based on different conditions for keys.
In these cases, conditional attributes works well. We can pass if
option to attributes
, attribute
, one
and many
. Below is an example for the same effect as filtering attributes section.
class User
attr_accessor :id, :name, :email, :created_at, :updated_at
def initialize(id, name, email)
@id = id
@name = name
@email = email
end
end
class UserResource
include Alba::Resource
attributes :id, :name, :email, if: proc { |user, attribute| !attribute.nil? }
end
user = User.new(1, nil, nil)
UserResource.new(user).serialize # => '{"id":1}'
After Alba.enable_inference!
called, Alba tries to infer root key and association resource name.
Alba.enable_inference!
class User
attr_reader :id
attr_accessor :articles
def initialize(id)
@id = id
@articles = []
end
end
class Article
attr_accessor :id, :title
def initialize(id, title)
@id = id
@title = title
end
end
class ArticleResource
include Alba::Resource
attributes :title
end
class UserResource
include Alba::Resource
key!
attributes :id
many :articles
end
user = User.new(1)
user.articles << Article.new(1, 'The title')
UserResource.new(user).serialize # => '{"user":{"id":1,"articles":[{"title":"The title"}]}}'
UserResource.new([user]).serialize # => '{"users":[{"id":1,"articles":[{"title":"The title"}]}]}'
This resource automatically sets its root key to either "users" or "user", depending on the given object is collection or not.
Also, you don't have to specify which resource class to use with many
. Alba infers it from association name.
Note that to enable this feature you must install ActiveSupport
gem.
You can set error handler globally or per resource using on_error
.
class User
attr_accessor :id, :name
def initialize(id, name, email)
@id = id
@name = name
@email = email
end
def email
raise RuntimeError, 'Error!'
end
end
class UserResource
include Alba::Resource
attributes :id, :name, :email
on_error :ignore
end
user = User.new(1, 'Test', 'email@example.com')
UserResource.new(user).serialize # => '{"id":1,"name":"Test"}'
This way you can exclude an entry when fetching an attribute gives an exception.
There are four possible arguments on_error
method accepts.
:raise
re-raises an error. This is the default behavior.:ignore
ignores the entry with the error.:nullify
sets the attribute with the error tonil
.- Block gives you more control over what to be returned.
The block receives five arguments, error
, object
, key
, attribute
and resource class
and must return a two-element array. Below is an example.
# Global error handling
Alba.on_error do |error, object, key, attribute, resource_class|
if resource_class == MyResource
['error_fallback', object.error_fallback]
else
[key, error.message]
end
end
You can control circular associations with within
option. within
option is a nested Hash such as {book: {authors: books}}
. In this example, Alba serializes a book's authors' books. This means you can reference BookResource
from AuthorResource
and vice versa. This is really powerful when you have a complex data structure and serialize certain parts of it.
For more details, please refer to test code
Currently, Alba doesn't support caching, primarily due to the behavior of ActiveRecord::Relation
's cache. See the issue.
When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
Alba.backend = :active_support
# or
Alba.backend = :oj_rails
The name "Alba" comes from "albatross", a kind of birds. In Japanese, this bird is called "Aho-dori", which means "stupid bird". I find it funny because in fact albatrosses fly really fast. I hope Alba looks stupid but in fact it does its job quick.
There are great pioneers in Ruby's ecosystem which does basically the same thing as Alba does. To name a few:
- ActiveModelSerializers a.k.a AMS, the most famous implementation of JSON serializer for Ruby
- Blueprinter shares some concepts with Alba
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/okuramasafumi/alba. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Alba project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.