Skip to content

Rails Serializers — Transforming Models to JSON

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Rails Serializers. We cover key concepts, practical examples, and best practices to help you master this topic.

Rails serializers transform ActiveRecord models to JSON following JSON API conventions, with support for attributes, relationships, conditional inclusion, and custom formatters.

What You'll Learn

By the end of this tutorial, you'll use JSONAPI::Serializer, define attributes and relationships, conditionally include fields, create custom formatters, and optimize serializer performance.

Why Serializers Matter

Serializers control the shape of API responses. They prevent exposing sensitive fields, format dates consistently, include related resources, and follow JSON API conventions.

Real-World Use

A product API uses ProductSerializer with name, price, and category. Admin users receive additional fields like cost and supplier. Reviews are included only when requested.

Serializers Path

flowchart LR
  A[Rails API] --> B[Serializers]
  B --> C[Attributes]
  B --> D[Relationships]
  B --> E[Conditional]
  B --> F[Performance]
  B --> G{You Are Here}
  style G fill:#f90,color:#fff

JSONAPI::Serializer

Use the jsonapi-serializer gem.

# Gemfile
gem "jsonapi-serializer"
# app/serializers/product_serializer.rb
class ProductSerializer
  include JSONAPI::Serializer

  attributes :name, :description, :price, :sku, :published
  attribute :formatted_price do |product|
    sprintf("$%.2f", product.price)
  end
  attribute :created_at do |product|
    product.created_at.iso8601
  end

  belongs_to :category, serializer: CategorySerializer
  has_many :reviews, serializer: ReviewSerializer
  has_many :variants
end

Conditional Attributes

Include attributes based on context.

class ProductSerializer
  include JSONAPI::Serializer

  attributes :name, :price, :description

  # Conditional attribute based on params
  attribute :cost_price, if: proc { |record, params|
    params[:current_user]&.admin?
  }

  attribute :supplier_info, if: proc { |_record, params|
    params[:include_supplier]
  }

  # Conditional relationship
  has_many :reviews, if: proc { |_record, params|
    params[:include_reviews]
  }
end

Custom Formatters

Create custom attribute formatters.

class ProductSerializer
  include JSONAPI::Serializer

  attributes :name

  attribute :price do |product|
    {
      amount: product.price.to_f,
      currency: product.currency || "USD",
      formatted: ActionController::Base.helpers.number_to_currency(product.price),
    }
  end

  attribute :availability do |product|
    if product.in_stock?
      { status: "in_stock", quantity: product.quantity }
    elsif product.backorderable?
      { status: "backorder", estimated: product.restock_date&.iso8601 }
    else
      { status: "out_of_stock" }
    end
  end
end

Pagination Meta

Include pagination metadata.

class ProductsController < ApplicationController
  def index
    products = Product.page(params[:page]).per(params[:per_page] || 20)
    render json: ProductSerializer.new(products).serializable_hash.merge(
      meta: {
        current_page: products.current_page,
        total_pages: products.total_pages,
        total_count: products.total_count,
      }
    )
  end
end

Common Mistakes

1. Serializing Sensitive Fields

Including password_hash, api_token, or internal IDs in serializers.

2. N+1 Queries via Serializer Relationships

Serializers access relationships that are not eager loaded. Use includes in queries.

3. Over-Serializing

Including every relationship and attribute slows responses. Include only what clients need.

4. Not Customizing Attribute Names

API consumers expect snake_case or camelCase. Customize attribute names for consistency.

5. Ignoring Serializer Cache

Serializers can be slow for large collections. Consider fragment Caching.

Practice Questions

1. What is a serializer used for?

Transforming models to JSON with controlled attribute and relationship inclusion.

2. How do you conditionally include an attribute?

Use the if: proc option on the attribute definition.

3. How do you include pagination metadata?

Merge meta hash into the serialized response.

4. How do you format an attribute value?

Use attribute :name do |record| block syntax.

5. Challenge: Create a serializer with conditional admin fields.

class UserSerializer
  include JSONAPI::Serializer

  attributes :id, :name, :email, :created_at

  attribute :role, if: proc { |_user, params|
    params[:current_user]&.admin?
  }

  attribute :last_login_at do |user|
    user.last_sign_in_at&.iso8601
  end
end

FAQ

What is the difference between jsonapi-serializer and AMS?

jsonapi-serializer is faster and follows JSON API spec. AMS is older.

Can I use serializers for non-API responses?

Yes. Serializers work for any JSON response, including Turbo frames.

How do I serialize a collection?

Serializer.new(@products).serializable_hash renders all items.

What is the JSON API spec?

A specification for building JSON APIs with conventions for includes, sparse fields, and errors.

How do I handle circular serialization?

Use lazy loading or limit relationship depth to prevent infinite loops.

Mini Project: Complete API Serialization

Build serializers for a blog API.

class PostSerializer
  include JSONAPI::Serializer

  attributes :title, :body, :slug, :published_at
  attribute :excerpt do |post|
    post.body.truncate(200)
  end

  belongs_to :author, serializer: UserSerializer
  has_many :comments, serializer: CommentSerializer

  has_many :tags do |post|
    post.tags.order(:name)
  end
end

What's Next

Rails Testing RSpec Rails Factory Bot Rails Faker

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro