0.0
The project is in a healthy, maintained state
Adequate Json is a gem that simplifies the process of serializing JSON for API responses.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 2.5
>= 12.3.3
~> 3.0

Runtime

 Project Readme

AdequateJSON

AdequateJSON is a serialization gem for Ruby on Rails APIs. It is easy to use, versatile, fast, and promotes API best practices.

Installation

Add this line to your application's Gemfile:

gem 'adequate_json'

And then execute:

$ bundle

Or install it yourself as:

$ gem install adequate_json

Usage

If you'd like to tinker with AdequateJSON on your own, see the sample Rails API.

At the controller level

If you're using — and you probably are — a controller inheriting ActionController::API, AdequateJSON adds a render_json method to controllers, to which you can provide a model instance, or a collection of models:

class CategoriesController < ActionController::API
  def index
    render_json Category.order(:name)
  end

  def show
    render_json Category.find(params[:id])
  end
end

AdequateJSON automatically infers the type of serializer it should use based on the model type, see the next section.

Your first serializer

When rendering a model, AdequateJSON searches for the corresponding serializing class in the Serializers module (or the module you specify using the configuration).

Each serializer is a class extending AdequateJSON::Base, and defining one or several variants to build (defaulting to :default for single objects and :no_wrapper for collections):

# app/serializers/category.rb

class Serializers::Category < AdequateJSON::Base
  builder do |json, category| # Same as builder(:default)
    json.category do
      json.(category, :id, :name, :created_at, :updated_at)
    end
  end

  builder(:no_wrapper) do |json, category|
    json.(category, :id, :name)
  end
end

AdequateJSON is based on Jbuilder. For all JSON manipulation methods available, have a look at Jbuilder's DSL documentation.

Choosing a variant

Builder variants default to :default for single objects, and :no_wrapper for collection items. To use another variant, specify it as a keyword argument:

class ProductsController < ActionController::API
  def index
    render_json Product.order(:name), variant: :header
  end

  def show
    render_json Product.find(params[:id]), variant: :full
  end
end

Reuse and composition

Each variant can be reused inside other variant using the serialize helper method, that runs on the same JSON builder. This allows for reuse of builders as partials, or make a variant "inherit" another one:

class Serializers::Product < AdequateJSON::Base
  builder do |json, product| # Same as builder(:default)
    json.product do
      serialize product, variant: :no_wrapper
    end
  end

  builder(:full) do |json, product|
    json.product do
      serialize product, variant: :no_wrapper
      serialize product.colors
      serialize product.sizes
      serialize product.category
    end
  end

  builder(:no_wrapper) do |json, product|
    serialize product, variant: :header
    json.(product, :description, :created_at, :updated_at)
  end

  builder(:header) do |json, product|
    json.(product, :id, :name, :price)
    serialize product.category
  end
end

Supported source objects

AdequateJSON uses built-in serializers for hashes and collections and, when serializing objects, will use the #model_name property to retrieve the name of the serializer to search for.

If you need to change the type of serializer for a given model, you may define the serializer method on the model:

class Book < ApplicationRecord
  # ...

  def serializer = :product
end

In last resort, you may also specify explicitely the serializer class to use:

render_json Serializers::Product.new(Book.find(params[:id])).to_builder

Serializing multiple objects at once

To serialize more complex structures, simply use a hash:

class ProductsController < ActionController::API
  def show
    render_json { product: Product.find(params[:id]), categories: Category.order(:name) }
  end
end

Pagination

As soon as you've added Kaminari to your Gemfile and paginate a collection, AdequateJSON automatically appends the pagination property to the JSON output:

class ProductsController < ActionController::API
  def index
    render_json Product.order(:name).page(params[:page]).per(10)
  end
end
{
  "collection": [
    {
      "id": "a9342787-0d24-43cf-8791-3a512f9e9bd4",
      ...
    },
    ...
  ],
  "pagination": {
    "current_page": 1,
    "total_count": 289,
    "next_page": 2,
    "previous_page": null,
    "total_pages": 29,
  }
}

If you'd like AdequateJSON to support other pagination gems, feel free to craft a pull-request or to open an issue, we'd be glad to help.

Rendering errors

Specify an error code, and AdequateJSON will try to localize it with a message found in one of your locales/*.yml files. The i18n scope defaults to api.errors, you may change it in the configuration. It also automatically renders errors based on ActiveModel messages.

class ProductsController < ApplicationController
  # ...

  def create
    product = Product.new(product_params)
    if product.save
      render_json product
    else
      render_error :invalid_model, product
    end
  end
end

Given a locale key api.errors.invalid_model, the render_error call would produce:

{
  "error": {
    "code": "invalid_model",
    "message": "The object couldn't be saved",
    "details": {
      "category": [
        "must exist"
      ],
      "name": [
        "can't be blank"
      ],
      "description": [
        "can't be blank"
      ],
      "price": [
        "can't be blank",
        "is not a number"
      ]
    }
  }
}

If you need to return errors for nested models and attributes, you may use the includes keyword argument:

render_error :invalid_model, product, includes: { category: :shop }

Configuration

All configuration options are available through a block yielded by the AdequateJSON.configure method.

The configuration code should take place in the application.rb file or one of the Rails environment configurations (production.rb, development.rb, etc.), not in an initializer:

module AdequateJsonSample
  class Application < Rails::Application

    AdequateJson.configure do |c|
      c.serializers_module :json # defaults to :serializers
      c.use_model_name_for_collection_key true  # defaults to `false`
      c.collection_key :list # defaults to `collection`
      c.i18n_errors_scope %i[controllers error] # defaults to `%i[api errors]`
    end
end

Performances

We've written a small benchmark to see how AdequateJSON performs compared to ActiveModelSerializers. The benchmark consists in serializing 10 times a collection of 10 000 objects (the lower the better) and displaying the min, max and average processing times:

$ bundle e ruby benchmark.rb
AdequateJSON - min: 75.68, max: 95.1 - average: 81.5 ms
ActiveModelSerializers - min: 471.04, max: 582.73 - average: 491.63 ms

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/EverestHC-mySofie/adequate_json.

License

The gem is available as open source under the terms of the MIT License.