Closed10

Ruby on RailsのJSON Serializer比較

iwamasaiwamasa

やりたいこと

Ruby on Railsを使用したREST API実装をする際に、JSONのシリアライズを行いたい。
※ModelのデータをJSONに整形して置き換えるとか

そのままControllerでレスポンスを整形することは可能だが、Controllerの役割が増える&実装の見通しが悪くなるのでレイヤーを分けて実装するようにしたい。

そこで、いくつか存在する手法をスクラップにまとめる。

iwamasaiwamasa

renderで自前実装

整形が特に不要であればこれで良さそう

# 1項目程度
render json: { user: @user }

# 固定文字
render json: { error: 'エラーだよ' }, status: :internal_server_error

# 配列
render json: { users: @users }

メリット

  • 直感的に実装ができる

デメリット

  • Controllerが肥大化する
iwamasaiwamasa

blueprinter

https://github.com/procore-oss/blueprinter

公式からサンプルを拝借

class UserBlueprint < Blueprinter::Base
  identifier :uuid

  fields :first_name, :last_name, :email
end

# 呼び出し
puts UserBlueprint.render(user)

レスポンス

{
  "uuid": "733f0758-8f21-4719-875f-262c3ec743af",
  "email": "john.doe@some.fake.email.domain",
  "first_name": "John",
  "last_name": "Doe"
}

メリット

  • JBuilderやActiveModelSerializersみたいに実装が可能
  • シンプル且つ、パフォーマンスも出るような設計になっている

デメリット

  • 他のライブラリと比較して文献少ないかも
  • ページネーションやキャッシュといった機能が盛り込まれていない
iwamasaiwamasa

Alba

https://github.com/okuramasafumi/alba

公式からサンプルを拝借

class UserResource
  include Alba::Resource

  root_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
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}'

メリット

  • 速いらしい
  • 実装は比較的容易らしい
  • 記事や採用事例は結構ありそう

デメリット

  • 比較的スター数が多くないかも(2024/09/12時点で926)
iwamasaiwamasa

jsonapi-serializer

https://github.com/jsonapi-serializer/jsonapi-serializer

元々はNetflixが開発したfast_jsonapiをフォークしたものらしい。(こちらは現在メンテされていない)

公式からサンプルを拝借

class MovieSerializer
  include JSONAPI::Serializer

  set_type :movie  # optional
  set_id :owner_id # optional
  attributes :name, :year
  has_many :actors
  belongs_to :owner, record_type: :user
  belongs_to :movie_type
end

# return a hash
hash = MovieSerializer.new(movie).serializable_hash

# return serialized JSON
json_string = MovieSerializer.new(movie).serializable_hash.to_json

メリット

  • 結構記事が豊富
  • 速いらしい

デメリット

  • 開発がちょっと止まっているかも?
    • 調査時点で2023/7/31が最終コミット
iwamasaiwamasa

Active Model Serializers

標準で入っているやつ
https://api.rubyonrails.org/classes/ActiveModel/Serialization.html

ActiveModel::SerializationActiveModel::Serializers::JSON の2種類あるらしい

実装例(JSONの方のみ)

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name

  def attributes
    {'name' => nil}
  end
end

person = Person.new
person.serializable_hash   # => {"name"=>nil}
person.as_json             # => {"name"=>nil}
person.to_json             # => "{\"name\":null}"

person.name = "Bob"
person.serializable_hash   # => {"name"=>"Bob"}
person.as_json             # => {"name"=>"Bob"}
person.to_json             # => "{\"name\":\"Bob\"}"

メリット

  • Rails標準である
  • 実装は比較的容易

デメリット

  • パフォーマンス面でやや劣る
iwamasaiwamasa

jbuilder

https://github.com/rails/jbuilder

公式からサンプルを拝借

ERB等のテンプレートエンジンと同じイメージだと分かりやすいかも

# app/views/messages/show.json.jbuilder

json.content format_content(@message.content)
json.(@message, :created_at, :updated_at)

json.author do
  json.name @message.creator.name.familiar
  json.email_address @message.creator.email_address_with_name
  json.url url_for(@message.creator, format: :json)
end

if current_user.admin?
  json.visitors calculate_visitors(@message)
end

json.comments @message.comments, :content, :created_at

json.attachments @message.attachments do |attachment|
  json.filename attachment.filename
  json.url url_for(attachment)
end

メリット

  • Rails標準
  • JSONのカスタマイズが柔軟に行える
  • テンプレートベース(ERB構文でロジックが組める)

デメリット

  • 大規模になったり、複雑化するとパフォーマンスが低下する
  • ビジネスロジックがview側に混在する恐れがある(ルールで縛れってお話しかも)
iwamasaiwamasa

jb

https://github.com/amatsuda/jb

jbuilderっぽい感じで実装ができて、jbuilderよりも高速

公式からサンプルを拝借

# app/views/messages/show.json.jb

json = {
  content: format_content(@message.content),
  created_at: @message.created_at,
  updated_at: @message.updated_at,
  author: {
    name: @message.creator.name.familiar,
    email_address: @message.creator.email_address_with_name,
    url: url_for(@message.creator, format: :json)
  }
}

if current_user.admin?
  json[:visitors] = calculate_visitors(@message)
end

json[:comments] = @message.comments.map do |comment|
  {
    content: comment.content,
    created_at: comment.created_at
  }
end

json[:attachments] = @message.attachments.map do |attachment|
  {
    filename: attachment.filename,
    url: url_for(attachment)
  }
end

json

メリット

  • jbuilderと比較して高速&シンプルな実装

デメリット

  • jbuilderと同様、ビジネスロジックがview側に混在する恐れがある
Hidden comment
このスクラップは3ヶ月前にクローズされました