A performant Ruby JSON Serializer DSL for Oj. ToJson uses the brand new Oj StringSerializer to provide the fastest performance and lowest possible memory footprint.
Why? Because current Ruby JSON serialisers take too long and use too much memory or can't express all valid JSON structures.
ToJson is ORM and ruby web framework agnostic and designed for serving fast and flexible JSON APIs.
ToJson is able to serialize an impressive 1.4 million operations a second on a 4 core laptop when running multiple ruby processes.
Add this line to your application's Gemfile:
Do this for now:
gem 'to_json', github: 'ahacking/to_json'
Eventually:
gem 'to_json'
And then execute:
$ bundle
Or install it yourself as:
$ gem install to_json
# args are optional
ToJson::Serializer.json!(args...) do |args...|
# DSL goes here, callers methods, helpers, instance variables and constants are all in scope
end
end
def index
@post = Post.all
# the rails responder will call to_json on the ToJson object
respond_with ToJson::Serializer.encode! do
# DSL goes here, contoller methods, helpers, instance variables and
# constants are all in scope
end
end
def index
@post = Post.all
# generate the json and pass it to render for sending to the client
render json: ToJson::Serializer.json! do
# DSL goes here, contoller methods, helpers, instance variables and
# constants are all in scope
end
end
def index
# just pass the collection (instead of the controller) to better support
# serializing Posts in different contexts and controllers. @foo is evil
render json: PostsSerializer.json!(Post.all)
end
The put
method is used to serialize named object values and
create arbitrarily nested objects.
All values will be serialized according to Oj processing rules.
put :title, @post.title
put :body, @post.body
put_fields @post, :title, :body
The DSL accepts array pairs, hashes, arrays containing any mix of array or hash pairs.
The following examples are all equivalent and map 'title' to 'the_tile' and 'created_at' to 'post_date' and leave 'body' as is.
put_fields @post, [:title, :the_title], :body, [:created_at, :post_date]
put_fields @post, [[:title, :the_title], :body, [:created_at, :post_date]]
put_fields @post, {title: :the_title, body: nil, created_at: :post_date}
put_fields @post, [:title, :the_title], :body, {:created_at => :post_date}
put_fields @post, {title: :the_title}, :body, {created_at: :post_date}
There are helpers to serialize object fields conditionally.
put_fields_unless_blank @post, :title: :body
put_fields_unless_nil @post, :title: :body
put_fields_unless :large?, @post, :title: :body
put_fields_if :allowed, @post, :title: :body
There are single field equivalents of the multiple field helpers. these take an optional mapping key and just like put they accept a block.
put_field @post, :title
put_field @post, :title, :the_title
put_field_unless_blank @post, :title, :the_title
put_field_unless_nil @post, :title, :the_title
put_field_unless :large? @post, :body
put_field_if :allowed? @post, :body
The long way:
put :post do
put :title, @post.title
put :body, @post.body
end
Using field helper:
```ruby
put :post do put_fields @post, :title :body end
The hash value under 'author' will be serialized directly by Oj.
put :author, {name: 'Fred', email: 'fred@example.com', age: 27}
The hash value will be serialized by Oj.
value {name: 'Fred', email: 'fred@example.com', age: 27}
put :latest_post, current_user.posts.order(:created_at: :desc).first do |post|
put_fields post, :title, :body
end
Arrays provide aggregation in JSON and are created with the array
method. Array
elements can be created through:
- literal value(s) passed to
array
without a block - evaluating blocks over the argument passed to array (similar to
each_with_index
) - evaluating a block with no argument
Within the array block, array elements can be created using value
, however this is
called implicitly for you when using put
or array
inside the array block.
The literal array value will be passed to Oj for serialization.
array ['Fred', 'fred@example.com', 27]
The @posts collection will be passed to Oj for serialization.
array @posts
array @posts do |post|
# calling put/put_* inside an array does an implicit 'value' call
# placing all named values into a single object
put_fields post, :title, post.body
end
array @posts do |post, index|
put_fields post, :title, post.body
put :position, index
end
Each post item will be processed and the email addresses of the author serialized.
array @posts do |post|
@post.author.emails.each do |email|
value email.address
end
end
The following example will an array containing 3 elements.
array do
value 'one'
value 2
value do
put label: 'three'
end
end
array do
value do
put :total_entries, @posts.total_entries
put :total_pages, @posts.total_pages
end
array @posts do
put :title, post.title
put :body, post.body
end
end
put :meta do
put_fields @posts, :total_entries, :total_pages
end
put :collection do
array @posts do |post| put_fields post, :title, :body end
end
put :_links do
put :self { put :href, url_for(page: @posts.current_page) }
put :first { put :href, url_for(page: 1) }
put :previous { @posts.current_page <= 1 ? nil : put :href, url_for(page: @posts.current_page-1) }
put :next { current_page_num >= @posts.total_pages ? nil : put :href, url_for(page: @posts.current_page+1) }
put :last { put :href, url_for(page: @posts.total_pages) }
end
array do
# this nested array is a single value in the outer array
array do
value 'a'
value 'b'
value 'b'
end
# this nested array is a single value in the outer array
array (1..3)
(1..4).each do |count|
# generate 'count' values in the nested array
count.times { value "item #{count}" }
end
end
end
def fullname(*names)
names.join(' ')
end
put :author, fullname(@post.author.first_name, @post.author.last_name)
# A Post model serializer, using ::ToJson::Serializer inheritance
class PostSerializer < ::ToJson::Serializer
include PostSerialization
# override the serialize method and use the ToJson DSL
# any arguments passed to encode! or json! are passed into serialize
def serialize(model)
put_post_nested model
end
end
# A Post collection serializer using include ToJson::Serialize approach
class PostsSerializer
include PostSerialization
def serialize(collection)
put_posts collection
end
end
# define a module so we can mixin Post model serialization concerns
anywhere and avoid temporary serializer objects for collection items
module PostSerialization
include ::ToJson::Serialize
# formatting helper
def fullname(*names)
names.join(' ')
end
def put_post(post)
put :title, post.title
put :body, post.body
put :author, fullname(post.author.first_name, post.author.last_name)
put :comments, CommentsSerializer.new(post.comments)
end
def put_post_nested(post)
put :post do
put_post(post)
end
end
def serialize_posts(posts)
put :meta do
put :total_entries, posts.total_entries
put :total_pages, posts.total_pages
end
put :collection, posts do |post|
put_post post
end
end
end
- Tests and more tests.
- API Documentation.
- Fork it
- 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 new Pull Request