Ruby JSON serializers benchmark
require "bundler/setup" | |
require "active_record" | |
require "logger" | |
require "oj" | |
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") | |
# ActiveRecord::Base.logger = Logger.new(STDOUT) | |
ActiveRecord::Schema.define do | |
create_table :posts, force: true do |t| | |
t.string :body | |
end | |
create_table :comments, force: true do |t| | |
t.integer :post_id | |
t.string :body | |
t.integer :commenter_id | |
end | |
create_table :users, force: true do |t| | |
t.string :name | |
end | |
end | |
class Post < ActiveRecord::Base | |
has_many :comments | |
has_many :commenters, through: :comments, class_name: 'User', source: :commenter | |
def attributes | |
{id: nil, body: nil, commenter_names: commenter_names} | |
end | |
def commenter_names | |
commenters.pluck(:name) | |
end | |
end | |
class Comment < ActiveRecord::Base | |
belongs_to :post | |
belongs_to :commenter, class_name: 'User' | |
def attributes | |
{id: nil, body: nil} | |
end | |
end | |
class User < ActiveRecord::Base | |
has_many :comments | |
end | |
require "alba" | |
Alba.backend = :oj | |
class AlbaCommentResource | |
include ::Alba::Resource | |
attributes :id, :body | |
end | |
class AlbaPostResource | |
include ::Alba::Resource | |
attributes :id, :body | |
many :comments, resource: AlbaCommentResource | |
attribute :commenter_names do | |
commenters.pluck(:name) | |
end | |
end | |
require "jbuilder" | |
class Post | |
def to_builder | |
Jbuilder.new do |post| | |
post.call(self, :id, :body, :comments, :commenter_names) | |
end | |
end | |
def commenter_names | |
commenters.pluck(:name) | |
end | |
end | |
class Comment | |
def to_builder | |
Jbuilder.new do |comment| | |
comment.call(self, :id, :body) | |
end | |
end | |
end | |
require "fast_jsonapi" | |
class FastCommentSerializer | |
include FastJsonapi::ObjectSerializer | |
attributes :id, :body | |
end | |
class FastPostSerializer | |
include FastJsonapi::ObjectSerializer | |
attributes :id, :body | |
attribute :commenter_names do |object| | |
object.commenters.pluck(:name) | |
end | |
has_many :comments, serializer: FastCommentSerializer | |
end | |
require "active_model_serializers" | |
class AMSCommentSerializer < ActiveModel::Serializer | |
attributes :id, :body | |
end | |
class AMSPostSerializer < ActiveModel::Serializer | |
attributes :id, :body | |
has_many :comments, serializer: AMSCommentSerializer | |
attribute :commenter_names | |
def commenter_names | |
object.commenters.pluck(:name) | |
end | |
end | |
require "blueprinter" | |
class CommentBlueprint < Blueprinter::Base | |
fields :id, :body | |
end | |
class PostBlueprint < Blueprinter::Base | |
fields :id, :body, :commenter_names | |
association :comments, blueprint: CommentBlueprint | |
def commenter_names | |
commenters.pluck(:name) | |
end | |
end | |
require "jsonapi" | |
require "jsonapi/renderer" | |
require "jsonapi/serializable" | |
class SerializablePost < JSONAPI::Serializable::Resource | |
type 'posts' | |
attributes :id, :body | |
attribute :commenter_names do | |
@object.commenters.pluck(:name) | |
end | |
has_many :comments do | |
attributes :data, :body | |
end | |
end | |
# require "jsonapi-resources" | |
# class PostResource < JSONAPI::Resource | |
# attributes :id, :body | |
# attribute :commenter_names do | |
# @object.commenters.pluck(:name) | |
# end | |
# has_many :comments do | |
# attributes :data, :body | |
# end | |
# end | |
require "representable" | |
class CommentRepresenter < Representable::Decorator | |
include Representable::JSON | |
property :id | |
property :body | |
end | |
class PostRepresenter < Representable::Decorator | |
include Representable::JSON | |
property :id | |
property :body | |
property :commenter_names | |
collection :comments | |
def commenter_names | |
commenters.pluck(:name) | |
end | |
end | |
post = Post.create!(body: 'post') | |
user1 = User.create!(name: 'John') | |
user2 = User.create!(name: 'Jane') | |
post.comments.create!(commenter: user1, body: 'Comment1') | |
post.comments.create!(commenter: user2, body: 'Comment2') | |
post.reload | |
alba = Proc.new { AlbaPostResource.new(post).serialize } | |
jbuilder = Proc.new { post.to_builder.target! } | |
fast_jsonapi = Proc.new { ActiveSupport::JSON.encode(FastPostSerializer.new(post).serializable_hash) } | |
ams = Proc.new { AMSPostSerializer.new(post, {}).to_json } | |
rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) } | |
blueprinter = Proc.new { PostBlueprint.render(post) } | |
jsonapi = Proc.new do | |
# Doesn't work | |
# renderer = JSONAPI::Serializable::Renderer.new | |
# renderer.render(post, class: {Post: SerializablePost}) | |
end | |
representable = Proc.new { PostRepresenter.new(post).to_json } | |
alba_inline = Proc.new do | |
Alba.serialize(post) do | |
attributes :id, :body | |
attribute :commenter_names do | |
commenters.pluck(:name) | |
end | |
many :comments do | |
attributes :id, :body | |
end | |
end | |
end | |
[alba, jbuilder, fast_jsonapi, ams, rails, blueprinter, jsonapi, representable, alba_inline].each {|x| puts x.call } | |
require 'benchmark' | |
time = 10000 | |
Benchmark.bmbm do |x| | |
x.report(:alba) { time.times(&alba) } | |
x.report(:jbuilder) { time.times(&jbuilder) } | |
x.report(:fast_jsonapi) { time.times(&fast_jsonapi) } | |
x.report(:ams) { time.times(&ams) } | |
x.report(:rails) { time.times(&rails) } | |
x.report(:blueprinter) { time.times(&blueprinter) } | |
x.report(:representable) { time.times(&representable) } | |
x.report(:alba_inline) { time.times(&alba_inline) } | |
end |
# frozen_string_literal: true | |
source "https://rubygems.org" | |
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } | |
gem "rails" | |
gem "sqlite3" | |
gem "jbuilder" | |
gem "active_model_serializers" | |
gem "fast_jsonapi" | |
gem "jsonapi" | |
gem "jsonapi-serializable" | |
gem "jsonapi-resources" | |
gem "blueprinter" | |
gem "representable" | |
gem "jsonapi-serializer" | |
gem "alba" | |
gem "ruby-prof" | |
gem "oj" | |
gem "multi_json" |
GEM | |
remote: https://rubygems.org/ | |
specs: | |
actioncable (6.0.3.2) | |
actionpack (= 6.0.3.2) | |
nio4r (~> 2.0) | |
websocket-driver (>= 0.6.1) | |
actionmailbox (6.0.3.2) | |
actionpack (= 6.0.3.2) | |
activejob (= 6.0.3.2) | |
activerecord (= 6.0.3.2) | |
activestorage (= 6.0.3.2) | |
activesupport (= 6.0.3.2) | |
mail (>= 2.7.1) | |
actionmailer (6.0.3.2) | |
actionpack (= 6.0.3.2) | |
actionview (= 6.0.3.2) | |
activejob (= 6.0.3.2) | |
mail (~> 2.5, >= 2.5.4) | |
rails-dom-testing (~> 2.0) | |
actionpack (6.0.3.2) | |
actionview (= 6.0.3.2) | |
activesupport (= 6.0.3.2) | |
rack (~> 2.0, >= 2.0.8) | |
rack-test (>= 0.6.3) | |
rails-dom-testing (~> 2.0) | |
rails-html-sanitizer (~> 1.0, >= 1.2.0) | |
actiontext (6.0.3.2) | |
actionpack (= 6.0.3.2) | |
activerecord (= 6.0.3.2) | |
activestorage (= 6.0.3.2) | |
activesupport (= 6.0.3.2) | |
nokogiri (>= 1.8.5) | |
actionview (6.0.3.2) | |
activesupport (= 6.0.3.2) | |
builder (~> 3.1) | |
erubi (~> 1.4) | |
rails-dom-testing (~> 2.0) | |
rails-html-sanitizer (~> 1.1, >= 1.2.0) | |
active_model_serializers (0.10.10) | |
actionpack (>= 4.1, < 6.1) | |
activemodel (>= 4.1, < 6.1) | |
case_transform (>= 0.2) | |
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) | |
activejob (6.0.3.2) | |
activesupport (= 6.0.3.2) | |
globalid (>= 0.3.6) | |
activemodel (6.0.3.2) | |
activesupport (= 6.0.3.2) | |
activerecord (6.0.3.2) | |
activemodel (= 6.0.3.2) | |
activesupport (= 6.0.3.2) | |
activestorage (6.0.3.2) | |
actionpack (= 6.0.3.2) | |
activejob (= 6.0.3.2) | |
activerecord (= 6.0.3.2) | |
marcel (~> 0.3.1) | |
activesupport (6.0.3.2) | |
concurrent-ruby (~> 1.0, >= 1.0.2) | |
i18n (>= 0.7, < 2) | |
minitest (~> 5.1) | |
tzinfo (~> 1.1) | |
zeitwerk (~> 2.2, >= 2.2.2) | |
alba (0.5.0) | |
blueprinter (0.25.0) | |
builder (3.2.4) | |
case_transform (0.2) | |
activesupport | |
concurrent-ruby (1.1.6) | |
crass (1.0.6) | |
declarative (0.0.20) | |
declarative-option (0.1.0) | |
erubi (1.9.0) | |
fast_jsonapi (1.5) | |
activesupport (>= 4.2) | |
globalid (0.4.2) | |
activesupport (>= 4.2.0) | |
i18n (1.8.5) | |
concurrent-ruby (~> 1.0) | |
jbuilder (2.10.0) | |
activesupport (>= 5.0.0) | |
jsonapi (0.1.1.beta6) | |
jsonapi-parser (= 0.1.1.beta3) | |
jsonapi-renderer (= 0.1.1.beta1) | |
jsonapi-parser (0.1.1.beta3) | |
jsonapi-renderer (0.1.1.beta1) | |
jsonapi-resources (0.10.2) | |
activerecord (>= 4.1) | |
concurrent-ruby | |
railties (>= 4.1) | |
jsonapi-serializable (0.1.3) | |
jsonapi-renderer (~> 0.1) | |
jsonapi-serializer (2.0.0) | |
activesupport (>= 4.2) | |
loofah (2.6.0) | |
crass (~> 1.0.2) | |
nokogiri (>= 1.5.9) | |
mail (2.7.1) | |
mini_mime (>= 0.1.1) | |
marcel (0.3.3) | |
mimemagic (~> 0.3.2) | |
method_source (1.0.0) | |
mimemagic (0.3.5) | |
mini_mime (1.0.2) | |
mini_portile2 (2.4.0) | |
minitest (5.14.1) | |
multi_json (1.15.0) | |
nio4r (2.5.2) | |
nokogiri (1.10.10) | |
mini_portile2 (~> 2.4.0) | |
oj (3.10.8) | |
rack (2.2.3) | |
rack-test (1.1.0) | |
rack (>= 1.0, < 3) | |
rails (6.0.3.2) | |
actioncable (= 6.0.3.2) | |
actionmailbox (= 6.0.3.2) | |
actionmailer (= 6.0.3.2) | |
actionpack (= 6.0.3.2) | |
actiontext (= 6.0.3.2) | |
actionview (= 6.0.3.2) | |
activejob (= 6.0.3.2) | |
activemodel (= 6.0.3.2) | |
activerecord (= 6.0.3.2) | |
activestorage (= 6.0.3.2) | |
activesupport (= 6.0.3.2) | |
bundler (>= 1.3.0) | |
railties (= 6.0.3.2) | |
sprockets-rails (>= 2.0.0) | |
rails-dom-testing (2.0.3) | |
activesupport (>= 4.2.0) | |
nokogiri (>= 1.6) | |
rails-html-sanitizer (1.3.0) | |
loofah (~> 2.3) | |
railties (6.0.3.2) | |
actionpack (= 6.0.3.2) | |
activesupport (= 6.0.3.2) | |
method_source | |
rake (>= 0.8.7) | |
thor (>= 0.20.3, < 2.0) | |
rake (13.0.1) | |
representable (3.0.4) | |
declarative (< 0.1.0) | |
declarative-option (< 0.2.0) | |
uber (< 0.2.0) | |
ruby-prof (1.4.1) | |
sprockets (4.0.2) | |
concurrent-ruby (~> 1.0) | |
rack (> 1, < 3) | |
sprockets-rails (3.2.1) | |
actionpack (>= 4.0) | |
activesupport (>= 4.0) | |
sprockets (>= 3.0.0) | |
sqlite3 (1.4.2) | |
thor (1.0.1) | |
thread_safe (0.3.6) | |
tzinfo (1.2.7) | |
thread_safe (~> 0.1) | |
uber (0.1.0) | |
websocket-driver (0.7.3) | |
websocket-extensions (>= 0.1.0) | |
websocket-extensions (0.1.5) | |
zeitwerk (2.4.0) | |
PLATFORMS | |
ruby | |
DEPENDENCIES | |
active_model_serializers | |
alba | |
blueprinter | |
fast_jsonapi | |
jbuilder | |
jsonapi | |
jsonapi-resources | |
jsonapi-serializable | |
jsonapi-serializer | |
multi_json | |
oj | |
rails | |
representable | |
ruby-prof | |
sqlite3 | |
BUNDLED WITH | |
2.1.4 |
user system total real | |
alba 2.820060 0.022638 2.842698 ( 2.895920) | |
jbuilder 3.771705 0.034647 3.806352 ( 3.901307) | |
fast_jsonapi 4.239389 0.041856 4.281245 ( 4.393856) | |
ams 4.844297 0.049414 4.893711 ( 5.092888) | |
rails 6.527143 0.065684 6.592827 ( 6.759237) | |
blueprinter 3.641437 0.036881 3.678318 ( 3.813814) | |
representable 4.165615 0.040480 4.206095 ( 4.318423) | |
alba_inline 4.100987 0.039418 4.140405 ( 4.300006) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Thanks for this good benchmark. Here are a few kind remarks :
Oj.optimize_rails
for optimal performance. You can add the line afterrequire "oj"
jsonapi-serializer
is a bit faster than originalfast_jsonpi
. It is the winner of your benchmark with an up to date bundle on my machine (I'm using ruby 2.6.6)alba
since 0.5