Ojを使ってArrayでラップしたオブジェクトをフィルターしたい件


Ruby

Rails環境なのを除外して、例えばRubyのオブジェクトをJSON化するときってto_jsonを実装してあーだらこーだらするわけですが
そのときにJSONに出力するキーとかをフィルターしたいとかってあると思うんですが...

require "oj"

class Sample
  def initialize(name)
    @id = 1
    @name = name
  end

  def as_json(options = {})
    data = { id: @id, name: @name }
    options[:keys] ? data.slice(*Array(options[:keys])) : data
  end

  def to_json(options = {})
    Oj.dump(as_json(options), mode: :compat)
  end
end

samples = [
  Sample.new("hoge"),
  Sample.new("fuga"),
  Sample.new("foobar")
]
puts Oj.dump(samples, mode: :rails, keys: [:name])

まあ要はdumpの引数にkeysを指定して出力するデータをsliceで切り出したりとかするわけですが、これ実行するとどうなるかっていうと

[{"name":"hoge"},{"id":1,"name":"fuga"},{"id":1,"name":"foobar"}]

ってわけになる。最初のオブジェクトにはちゃんとsliceで切り出せれてるけどそれ以降ではそれが正しく作用してない。
これ何が根本的な原因なのかはちょっと不明なんだけど、mode: :railsをするとas_jsonに渡される引数にちゃんと伝番されないっぽい

require "oj"

class Sample
  def initialize(name)
    @id = 1
    @name = name
  end

  def as_json(options = {})
    data = { id: @id, name: @name }
    options[:keys] ? data.slice(*Array(options[:keys])) : data
  end

  def to_json(options = {})
    Oj.dump(as_json(options), mode: :compat)
  end
end

samples = [
  Sample.new("hoge"),
  Sample.new("fuga"),
  Sample.new("foobar")
]
# mode:を:compatにする
puts Oj.dump(samples, mode: :compat, keys: [:name])

これで実行すると

[{"name":"hoge"},{"name":"fuga"},{"name":"foobar"}]

っていう感じになる。ということで配列でラップしたオブジェクトを実装したto_jsonなどを使って出力する場合がある場合にはここらへん注意かもね

ちなみに多分RailsっていうかActiveSupport自体にはこういうのがあるっぽいけど面倒くさいので省略

余談

mode: :railsのときにas_jsonの引数に渡されるのが

{:mode=>:rails, :keys=>[:name]}
{}
{}

mode: :compatのときにas_jsonの引数に渡されるのが

{:mode=>:compat, :keys=>[:name]}
{:mode=>:compat, :keys=>[:name]}
{:mode=>:compat, :keys=>[:name]}

というようにmode: :railsしてるときにちゃんとas_jsonの引数で受け取れない場合があるっぽい

関連記事