Edited at

扱いにくい階層の深いHashをフラット(1階層)にする

More than 5 years have passed since last update.

APIなどで階層の深いHashを受け取り、そのデータをアプリケーションで利用する際に、レスポンスデータがどんなデータパターンで来てもエラーにならないように設計するのは以外と難しいことがあるかと思います。

そこで、受け取った階層の深いHashをフラットにして1階層に変換する機能を作ったのでニーズがある方は使ってみてください。


/lib/utility/hash_flatter.rb


#今回は/libにutilityフォルダを作成してそこに作りました。

class HashFlatter < Object
def set(hash)
@hash = hash
@flatted = false
end
def flat
@flatted = true
@ret = {}
@hash.each do |key, value|
if value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
value.each do |key_s, value_s|
@ret = @ret.merge({key.to_s + '[' + key_s.to_s + ']' => value_s})
@flatted = false
end
else
if value.class == Array
value.each_with_index do |element, index|
@ret = @ret.merge ({key+'['+index.to_s+']' => element})
@flatted = false
end
else
@ret = @ret.merge ({key => value})
end
end
end
end
def get
@ret
end
def flatted?
@flatted
end

def self.flat hash
hf = HashFlatter.new
hf.set hash
hf.flat
while hf.flatted? == false
hf.set hf.get
hf.flat
end
hf.get
end

end



/config/appication.rb


#この一行を追加しておけば、いつでもHashFlatterが使えます。
config.autoload_paths += %W(#{config.root}/lib/utility)


実際に使ってみると、こんな感じに。

hash=

{"name"=>"Toshiyuki Oka",
"age"=>"29",
"location"=>
{"country"=>"Japan",
"city"=>"Tokyo",
"other"=>
{"detail"=>"Minatoku-Minamiazabu",
"building"=>"Furukawa building"
}
}
}

HashFlatter.flat(hash)
=> {"name"=>"Toshiyuki Oka", "age"=>"29", "location[country]"=>"Japan", "location[city]"=>"Tokyo", "location[other][detail]"=>"Minatoku-Minamiazabu", "location[other][building]"=>"Furukawa building"}

どんなときに特に役に立つかというと、例えば、locationが必須データではなかったときに、

hash["location"]

=> nil

user.location.country = hash["location"]["city"]
=> NoMethodError: You have a nil object when you didn't expect it!

となってしまい、それを回避するためにはいくつかの条件分岐をしなければなりません。

もちろん可読性が低くなり、エラーの原因にもなります。

HashFlatterを使えば、hash["location"]がnilでも、

#hash["location"]=nilのときは下記のようになり、

hash["location"]
=> nil

HashFlatter.flat(hash)
=> {"name"=>"Toshiyuki Oka", "age"=>"29", "location"=>nil}

#hash["location"]!=nilのときは下記のようになるので、条件分けが必要ありません。
hash["location"]
=> {"country"=>"Japan", "city"=>"Tokyo", "other"=>{"detail"=>"Minatoku-Minamiazabu", "building"=>"Furukawa building"}}

HashFlatter.flat(hash)
=> {"name"=>"Toshiyuki Oka", "age"=>"29", "location[country]"=>"Japan", "location[city]"=>"Tokyo", "location[other][detail]"=>"Minatoku-Minamiazabu", "location[other][building]"=>"Furukawa building"}

以上、ニーズに合致する方がいたら使ってみてください!!