Edited at

JSON処理をわかりやすくするGemを作った

More than 1 year has passed since last update.


背景


RubyでのJson処理

RubyでAPIを叩いて処理するというコードを書くときがあるとします。

そのAPIレスポンスがJSONで以下のように帰ってくるとします。


sample.json

{

"id": 1,
"title": "テスト記事",
"description": "説明文",
"writer": {
"id": 1,
"name": "なまえ",
"profile": "プロフィール ",
"picture": {
"id": 1,
"url": "https://test.jp/test.jpg"
},
"tags": [
{
"id": 1,
"name": "Ruby"
},
{
"id": 2,
"name": "Kotlin"
}
],
"picture": {
"id": 1,
"url": "https://test.jp/test.jpg"
}
}

これをRubyで処理して、Articleとして保存というようなのを書こうとすると

url = 'APIのURL'

uri = URI::parse(url)
res = Net::HTTP.get(uri)
result = JSON.parse(res)

Post.create(title: result['title'], img_url: result['picture']['url'])

result['tags'].each do |tag|
HashTag.create(name: tag['name'])
end

という風に書くことになると思います。

こういったようにハッシュ化されたJSONを扱う時以下のようなことが問題になります


  • レスポンスの形式が何処かにまとまっていない、書いた人の頭のなかにある

  • JSONが複雑になるほどコードが大変になる

  • テストしにくい


AndroidでのJson処理

Androidでは以下のように行えます。

※ KotlinでのRetrofit + Moshiの場合

data class Article(val id: Int,

val title: String,
val description: String,
val writer: Writer,
val tags: List<Tag>,
val picture: Picture)

data class Writer(val id: Int,
val name: String,
val profile: String,
val picture: Picture)

data class Tag(val id: Int,
val name: String)

data class Picture(val id: Int,
val url: String)

上記の用に定義しておけばJSON形式のレスポンスを、POJO(Plain Old Java Object)へ変換してくれます。

APIのレスポンスをオブジェクトにして持っておくことで、色々な処理が楽になります。

今回作ったライブラリの目的は、JSON形式のレスポンスを、PORO(Plain Old Ruby Object)へ変換することです。

こういったような処理をRubyでも実現して、Json周りの簡略化を行おうとしました。


作ったライブラリ

https://rubygems.org/gems/ruson

https://github.com/klriutsa/ruson


使い方

Ruson::Baseを継承したモデルを作ります

fieldsというメソッドを定義して、その中にfieldを定義していきます。

class Article < Ruson::Base

def fields
field :id
field :title
field :description
field :writer, class: Writer
field :tags, each_class: Tag
field :picture, class: Picture
end
end

class Writer < Ruson::Base
def fields
field :id
field :name
field :profile
field :picture, class: Picture
end
end

class Tag < Ruson::Base
def fields
field :id
field :name
end
end

class Picture < Ruson::Base
def fields
field :id
field :url
end
end

これらを使ってさっきのコードを書き直すと

url = 'APIのURL'

uri = URI::parse(url)
res = Net::HTTP.get(uri)
article = Article.new(res)

Post.create(title: article.title, img_url: article.picture.url)

article.tags.each do |tag|
HashTag.create(name: tag.name)
end

という風にわかりやすく書くことができます

また作成したオブジェクトにメソッドを持たせて

class Article < Ruson::Base

def fields
field :id
field :title
field :description
field :writer, class: Writer
field :tags, each_class: Tag
field :picture, class: Picture
end

def save
Post.create(title: title, img_url: picture.url)
end
end

という風に書けばロジックをオブジェクトに移せるので可読性が上がるかもしれません。


具体的な使い方


基本

class Article < Ruson::Base

def fields
field :id
field :title
end
end

fieldで指定したものは、

article.id

article.title

と呼べます


ルートパラメータがある時

{

"article": {
"id": 1,
"title": "テスト記事",
"description": "説明文"
}
}

article = Article.new(res, root: 'article')

と指定して下さい


名前を変えたい時

class Article < Ruson::Base

def fields
field :id
field :text, name: 'title'
end
end

とすればJsonのtitleという要素をtextに入れてくれます。


ネストしたクラスがある時

class Article < Ruson::Base

def fields
field :writer, class: Writer
end
end

上記の用に指定すると、指定したクラスのオブジェクトに変換します。


ネストしたクラスのリストがある時

class Article < Ruson::Base

def fields
field :tags, each_class: Tag
end
end

上記の用に指定すると、指定したクラスのオブジェクトの配列に変換します。


つぶやき


  • 本当は、StringとかInteger、Nullableなどいろいろ指定したかったんですが、RubyにBooleanクラスがなくて、そこで躓いたので、一旦は断念。

  • fieldsで囲む必要が無いようにしたかったけど、方法が思いつかなかったので今の方法にしました。

  • もしかすると似たようなライブラリがあるかもしれないです

明日は@higopageさんの「Unityアプリを作ったときにやったことをまとめました。」です。