З того часу, як JavaScript став основним інструментом для роботи в вебі, серіалізація JSON стала важливою частиною багатьох веб-застосунків. У статті я поясню, що таке JSON-серіалізація, та чому вона необхідна у Rails-застосунках, а також як використовувати наявні бібліотеки для серіалізації JSON лаконічно та ефективно. Почну з пояснень, потім обговоримо деякі з найбільш популярних рішень і, нарешті, змоделюємо впровадження JSON-серіалізації, використовуючи оптимальні рішення в Ruby on Rails застосунках.
Для повного розуміння наведеного у статті коду будуть корисними базові знання Ruby.
Що таке JSON-серіалізація?
JSON (JavaScript Object Notation) — формат обміну даних, що представляє об'єкти у вигляді рядків. Такий формат даних може легко передаватися між серверами. Серіалізація — процес, що перетворює об'єкт у рядок, який передається.
Навіщо JSON-серіалізація у Rails-застосунках?
Якщо у вашому Rails-застосунку є API, що використовує JSON, його можна застосовувати з популярними JavaScript фреймворками так само, як і будь-який застосунок, що підтримує JSON.
Два етапи JSON-серіалізації
Процес серіалізації формату JSON складається з двох етапів: підготовка даних та безпосередньо перетворення у формат JSON.
Підготовка даних
Підготовка даних являє собою перетворення об'єктів Ruby у hash-таблицю. Якщо у вас є модель класу Person
, що містить email, та ім'я, то атрибути об'єктів, готові для серіалізації, будуть виглядати так:
{"email" => "john@doe.com", "name" => "John Doe"}
Зауважте, що в Rails існує можливість викликати метод #to_json
для екземпляра. У результаті буде повернено hash-таблицю з усіма атрибутами. Доволі часто потреба в атрибутах моделі відсутня, тому важливо підготувати hash-таблицю лише з тими даними, що будуть необхідні. Подібна поведінка — частина хорошого тону підготовки даних.
Нижче наведено повний перелік правил хорошого тону, яких необхідно дотримуватись при підготовці даних:
-
Обирайте лише ті атрибути, які справді необхідні у JSON-відповіді. Найпростіше реалізувати це у Rails шляхом виклику
#to_json
для екземпляра моделі класу та передати тільки:option
, що зберігає масив назв атрибутів, які ви дійсно потребуєте:person.to_json(only: [:email, :name])
-
Розгляньте можливість скорочення змісту. Якщо необхідно відобразити багато записів у списку, можна скоротити опис або значення, щоб помістити більше записів на сторінці. Логіка такого скорочення найкраще виконується на серверній частині, тому фронт-енд відповідає лише за відображення інформації.
-
Переконайтеся в тому, що назви атрибутів інформативні. Якщо ви працюєте з готовим застосунком та маєте справу з погано названими атрибутами, подбайте про використання більш описових імен у JSON-відповідях. Таким чином легше визначити, що містить поле, подивившись лише на його назву.
-
Вибирайте асоціації з розумом. Подумайте, чи дійсно атрибут є необхідним перед додаванням асоціації. Завантаження непотрібних асоціацій значно уповільнює процес підготовки даних.
Найбільш відомі інструменти для підготовки даних
Існує багато пакетів, що застосовуються при JSON-серіалізації у Ruby on Rails застосунках. Їх API розроблено аналогічно; однак, існують деякі важливі відмінності. Я огляну найбільш популярні інструменти підготовки даних та порівняю їх показники, щоб визначити найкращий.
З метою тестування я буду використовувати дві моделі класів: Post
та Comment
.
Як повторити
Якщо хочете самостійно протестувати приклади коду, наведені нижче, можете створити новий Rails-проект, використовуючи наступний код:
rails new jsontest
cd jsontest
bundle exec rake db:create
bundle exec rails g model post title:string content:text published:boolean
bundle exec rails g model comment author:string body:text post_id:integer
bundle exec rake db:migrate
# app/models/post.rb
class Post < ActiveRecord::Base
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
end
# Завантажимо тестові дані — bundle exec rails c
post = Post.create!(title: "Post", content: "content", published: true)
Comment.create!(post: post, author: "Author", body: "Comment")
** Active Model Serializers**
Реалізація ActiveModel::Serializer
є дуже популярною, незважаючи на те, що остаточна версія ще не була випущена. Поточною стабільною версією є 0.10, але й вона проходить етап усунення помилок.
Додаємо пакет з бібліотекою до нашого gem-файлу:
gem 'active_model_serializers', '~> 0.10.0'
Створимо серіалізатори для наших моделей Post
та Comment
:
rails g serializer post
rails g serializer comment
Серіалізатори створено у директорії app/serializers
. Перед їх використанням необхідно повідомити Rails, що вони повинні бути доступні автоматично. Для цього відредагуйте файл config/application.rb
, додавши наступний рядок:
config.autoload_paths += ["#{config.root}/app/serializers"]
Переконайтеся, що ваші серіалізатори мають такий зміст:
# app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
has_many :comments
attributes :id, :title, :content
end
# app/serializers/comment_serializer.rb
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body, :author
end
Протестуємо їх:
post = Post.joins(:comments).first
PostSerializer.new(post).as_json # => {:id=>12, :title=>"Post", :content=>"content", :comments=>[{:id=>201, :body=>"Comment", :author=>"Author"}]}
JSONAPI-RB
JSONAPI-RB — інтуїтивно зрозуміла бібліотека Ruby, що складається із чотирьох незалежних мікробібліотек:jsonapi-parser, jsonapi-renderer, jsonapi-serializable та jsonapi-deserializable.
Додамо пакет з бібліотекою до нашого Gemfile:
gem 'jsonapi-rails'
Оновлюємо серіалізатори, які збережено за шляхами app/serializers/post_serializer.rb
та app/serializers/comment_serializer.rb
:
# app/serializers/post_serializer.rb
class PostSerializer < JSONAPI::Serializable::Resource
type 'posts'
has_many :comments
attributes :id, :title, :content
end
# app/serializers/comment_serializer.rb
class CommentSerializer < JSONAPI::Serializable::Resource
type 'comments'
attributes :id, :author, :body
end
Ми готові протестувати їх роботу:
post = Post.joins(:comments).first
renderer = JSONAPI::Serializable::Renderer.new
renderer.render(post, class: { Post: PostSerializer, Comment: CommentSerializer }, include: [:comments]) # => {:data=>{:id=>"113", :type=>:posts, :attributes=>{:id=>113,
:title=>"Post",
:content=>"content"},
:relationships=>{:comments=>{:data=>[{:type=>:comments,
:id=>"2702"}]}}}, :included=>[{:id=>"2702", :type=>:comments,
:attributes=>{:id=>2702, :author=>"Author", :body=>"Comment"}}]}
Fast JSON API
Блискавично швидкий JSON:API серіалізатор від Netflix. Додамо пакет з бібліотекою до нашого Gemfile: gem 'fast_jsonapi'
Наступним кроком буде оновлення наших серіалізаторів з метою використання Fast JSON API пакету:
# app/serializers/post_serializer.rb
class PostSerializer
include FastJsonapi::ObjectSerializer
attributes :title, :content
has_many :comments
end
# app/serializers/comment_serializer.rb
class CommentSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :body, :author
end
Наразі можемо підготувати дані для серіалізації:
post = Post.joins(:comments).first
PostSerializer.new(post, include: [:comments]).serializable_hash # =>
{:data=>{:id=>"12", :type=>:post, :attributes=>{:title=>"Post",
:content=>"content"}, :relationships=>{:comments=>{:data=>[{:id=>"201", :type=>:comment}]}}}, :included=>[{:id=>"201", :type=>:comment,
:attributes=>{:id=>201, :body=>"Comment", :author=>"Author"}}]}
RABL
RABL (Ruby API Builder Language) — система шаблонів у Ruby для генерації JSON.
За звичкою, спочатку додаємо пакет до Gemfile:
gem 'rabl'
Цього разу не визначаємо серіалізатор за шляхом app/serializers
, натомість створюємо шаблон JSON. Додаємо нові файли:app/views/post.rabl
та app/views/comment.rabl
.
# app/views/post.rabl
object @job
attributes :id, :title, :content
child :comments do
extends "comment"
end
# app/views/comment.rabl
object @comment
attributes :id, :author, :body
За замовчуванням пакет автоматично використовує створені шаблони у Rails контролерах при запиті JSON-відповіді. Однак, можливо також підготувати дані на рівні консолі:
post = Post.joins(:comments).first
Rabl.render(post, 'post', :view_path => 'app/views', :format => :hash) #
=> {:id=>12, :title=>"Post", :content=>"content", :comments=>[{:id=>201, :author=>"Author", :body=>"Comment"}]}
JSON API Serializers
JSONAPI::Serializers
— проста бібліотека для серіалізації об'єктів Ruby.
Додайте наступний рядок до Gemfile:
gem 'jsonapi-serializers'
Далі оновлюємо вже створені серіалізатори:
# app/serializers/post_serializer.rb
class PostSerializer
include JSONAPI::Serializer
attribute :id
attribute :title
attribute :content
has_many :comments
end
# app/serializers/comment_serializer.rb
class CommentSerializer
include JSONAPI::Serializer
attribute :id
attribute :author
attribute :body
end
Тепер можемо генерувати дані з JSON-відповіді:
post = Post.joins(:comments).first
JSONAPI::Serializer.serialize(post, include: ['comments']) # =>
{"data"=>{"type"=>"posts", "id"=>"12", "attributes"=>{"id"=>12,
"title"=>"Post", "content"=>"content"}, "links"=>{"self"=>"/posts/12"},
"relationships"=>{"comments"=>{"links"=>{"self"=>"/posts/12/relationships/comments", "related"=>"/posts/12/comments"},
"data"=>[{"type"=>"comments", "id"=>"201"}]}}},
"included"=>[{"type"=>"comments", "id"=>"201",
"attributes"=>{"id"=>201,
"author"=>"Author", "body"=>"Comment"},
"links"=>{"self"=>"/comments/201"}}]}
JBuilder
JBuilder — пакет, що надає прості DSL (Domain Specific Languages) для оголошення структур JSON. Зараз нема потреби оновлювати наш Gemfile, тому що пакет вже встановлено за замовчуванням у Rails. Зазвичай структури зберігаються у файлі з розширенням json.jbuilder
. Тож створимо файл app/views/post2.json.jbuilder
та додамо туди наступний код:
json.id post.id
json.title post.title
json.content post.content
json.comments(post.comments) do |comment|
json.id comment.id
json.author comment.author
json.body comment.body
end
Можна завантажити шаблон та генерувати JSON або не користуватися шаблоном:
post = Post.joins(:comments).first
# With template
renderer = ApplicationController.new
renderer.render_to_string('/post2', locals: {post: post}) # => "{\\"id\\":114,\\"title\\":\\"Title 0\\",\\"content\\":\\"Content 0\\",\\"comments\\":[{\\"id\\":2727,\\"author\\":\\"Author 24\\",\\"body\\":\\"Comment 24\\"}]}"
# Without template
def jbuild(*args, &block)
Jbuilder.new(*args, &block).attributes!
end
result = jbuild do |json|
json.id post.id
json.title post.title
json.content post.content
json.comments(post.comments) do |comment|
json.id comment.id
json.author comment.author
json.body comment.body
end
end
result # => {"id"=>12, "title"=>"Post", "content"=>"content", "comments"=>[{"id"=>201, "author"=>"Author", "body"=>"Comment"}]}
Серіалізація
Тепер, коли ми закінчили із тестуванням різних пакетів Ruby, переходимо до наступного етапу серіалізації JSON: трансформації хеш-таблиць у формат JSON. Найбільш популярні рішення:
Oj
Oj — швидкий JSON-парсер та об'єктний маршалер (marshaller) Ruby. Для його встановлення додаємо один рядок до Gemfile:
gem 'oj'
Можемо спробувати перетворити хеш-таблицю на формат JSON:
hash = {name: "John Doe", email: "john@doe.com"}
Oj.dump(hash) # => "{\\"name\\":\\"John Doe\\",\\"email\\":\\"john@doe.com\\"}"
JSON
Бібліотека JSON є стандартною для здійснення серіалізації в Ruby, тому можемо використовувати її прямо в нашій консолі, без додавання стороннього коду:
hash = {name: "John Doe", email: "john@doe.com"}
JSON.generate(hash) # => "{\\"name\\":\\"John Doe\\",\\"email\\":\\"john@doe.com\\"}"
Yajl
Yajl — потокова JSON-бібліотека синтаксичного аналізу та кодування для Ruby.
Перед використанням треба додати пакет:
gem 'yajl-ruby'
Наразі можемо серіалізувати приклад хешу:
require 'yajl'
hash = {name: "John Doe", email: "john@doe.com"}
Yajl::Encoder.encode(hash) # => "{\\"name\\":\\"John Doe\\",\\"email\\":\\"john@doe.com\\"}"
Найкраще рішення для JSON-реалізації у Rails
Ми оглянули найбільш популярні інструменти для підготовки інформації та серіалізації JSON. Час обрати найбільш продуктивну пару. Я порівняю швидкість підготовки даних та JSON-серіалізації, використовуючи код із прикладів вище.
Для тесту я створив 100 об'єктів Post
, кожен з яких матиме по 25 коментарів:
100.times do |i|
pos = Post.create!(title: "Title #{i}", content: "Content #{i}")
25.times do |i2|
Comment.create!(post: post, author: "Author #{i2}", body: "Comment #{i2}")
end
end
Щоб порівняти час підготовки даних, необхідний для кожного інструменту, я використаю модуль Ruby Benchmark.
Інструменти підготовки даних
Нижче наведено рейтинг рішень від найшвидшого до найповільнішого:
Назва | Час (секунди) |
---|---|
FAST JSON API | 0.121921 |
Active Model Serializers | 0.154672 |
JSONAPI-RB | 0.246346 |
JSON API Serializers | 0.262120 |
RABL | 0.853417 |
JBuilder | 2.559193 |
Найшвидшим виявився Fast JSON API, а найповільнішим — JBuilder та RABL.
На щастя, FAST JSON API досить зрозумілий на інтуїтивному рівні та не потребує додаткових налаштувань. Все, що необхідно для початку роботи з ним — визначити атрибути та асоціації для даної моделі.
Код, що тестувався, можна переглянути тут.
Рішення для серіалізації JSON
Назва | Час (секунди) |
---|---|
Oj | 0.007225 |
Yajl | 0.014289 |
JSON | 0.036572 |
Найшвидшою виявилася бібліотека Oj, що у декілька разів спритніша за опонентів. Тож Oj — безумовний лідер.
Впровадження швидкої JSON-серіалізації до Ruby on Rails застосунку
В результаті тестування перемогла пара інструментів: Fast JSON API та Oj. Час застосувати обидва рішення у застосунку Ruby on Rails, щоб продемонструвати показову JSON-серіалізацію.
Почнемо з додавання необхідних пакетів до нашого Gemfile:
gem 'oj'
gem 'fast_jsonapi'
Створення контролеру
Спочатку було створено гілку об'єктів Post
із коментарями. Час дійти кінцевої точки — перетворити інформацію у формат JSON. Ддодамо новий клас контролеру app/controllers/posts_controller.rb
з наступним змістом:
class PostsController < ApplicationController
def index
posts = Post.joins(:comments)
end
end
Створення серіалізатору
Для того, щоб провести серіалізацію об'єктів, необхідно визначити серіалізатор з необхідними для перетворення атрибутами. Наведений код виглядає досить знайомо, тому що ми вже використовували його при демонстрації пакетів:
# app/serializers/post_serializer.rb
class PostSerializer
include FastJsonapi::ObjectSerializer
attributes :title, :content
has_many :comments
end
# app/serializers/comment_serializer.rb
class CommentSerializer
include JSONAPI::Serializer
attribute :id
attribute :author
attribute :body
end
Використання OJ
Відтоді, як пакет fast_jsonapi
автоматично включає Oj, немає потреби явно вказувати застосунку Rails включати пакет.
Підготовка відповіді у форматі JSON
Наші серіалізатори підготовлено, тому залишилося відобразити повідомлення у форматі JSON. Відредагуємо попередньо створений контролер:
class PostsController < ApplicationControlle
def index
posts = Post.joins(:comments)
render json: PostSerializer.new(posts).serialized_json
end
end
Сповістимо Rails, що ми хочемо отримати до нього доступ, відредагувавши файл config/routes.rb
:
resources :posts, only: [:index]
Запускаємо команду rails s
та отримуємо доступ до згенерованої інформації у форматі JSON, використовуючи URL.
Ключові моменти
У статті ми:
- Порівняли шість найбільш популярних інструментів підготовки інформації для JSON-серіалізації та обрали найкращий.
- Порівняли три найбільш популярні інструменти для JSON-серіалізації та виявили найкращий.
- Реалізували механізм швидкої JSON-серіалізації у застосунку Ruby on Rails, використовуючі найшвидші з доступних рішень.
Як наслідок, отримали широкий та надзвичайно швидкий механізм. Такий підхід розміщує логіку серіалізації на рівні моделі й вимагає від нас прямої вказівки, який серіалізатор необхідно використовувати. Важливо, що нема потреби у додаткових налаштуваннях, щоб почати роботу. Використання базового коду достатньо для забезпечення завершеної JSON-відповіді у контролерах.
Ще немає коментарів