#はじめに
###やりたいこと
TSVファイルをJSON形式に、またJSONをTSVファイルに変換
###環境
Ruby 2.6.5
Mac OS 10.15.5
###この記事内で使用するサンプル
[{"name":"john","gender":"m","age":"18"},
{"name":"paul","gender":"m","age":"20"},
{"name":"alice","gender":"f","age":"15"},
{"name":"dabid","gender":"m","age":"17"},
{"name":"jasmin","gender":"f","age":"17"}]
Name gender age
john m 18
paul m 20
alice f 15
dabid m 17
jasmin f 17
#JSONからTSVへ変換する方法
require "json" #JSONを扱うために記述
File.open("meibo.json") do |json| #JSONファイルを開く
File.open("meibo.txt", "w") do |txt| #出力先のTSVファイルを開く
array = JSON.load(json) #JSONの読み込み
column = ["name","gender","age"] #ヘッダー
txt.puts(column.join("\t")) #ヘッダを入れる
array.each do |line| #JSON配列の各要素をTSVファイルの各行に入れる
attr = [line["name"],line["gender"],line["age"]]
txt.puts(attr.join("\t"))
end
end
end
まずは、JSONファイルを開きます。出力先のTSVファイルも書き込みモードで開いておきます。
次に、JSONファイルを読み込んでいきます。下記のように、JSONはJSON.load([Fileオブジェクト])
でJSONファイルを読み込んで、ハッシュを要素とする配列で返してくれます。
require "json"
File.open("meibo.json") do |json|
array = JSON.load(json)
p array
end
#=>
[{"name"=>"john", "gender"=>"m", "age"=>"18"}, {"name"=>"paul", "gender"=>"m", "age"=>"20"}, {"name"=>"alice", "gender"=>"f", "age"=>"15"}, {"name"=>"dabid", "gender"=>"m", "age"=>"17"}, {"name"=>"jasmin", "gender"=>"f", "age"=>"17"}]
返ってきた配列をeach
メソッドで回し、それぞれのハッシュをTSVでの一行一行にしていくイメージです。
TSVの各行に入れていくテクニックとしては、each
で取り出した各ハッシュのバリューを、キーを直接指定するかたちで取り出し、それらを一度、配列に入れ直します。そして、join
メソッドを用いてタブ区切りで配列を結合し、その文字列をTSVのファイルの各行に挿入していきます。
出力結果は以下の通りです。
name gender age
john m 18
paul m 20
alice f 15
dabid m 17
jasmin f 17
###ハマったところ
最初は以下のように、JSON.load
したあとの配列をeach
処理に回す際、各オブジェクトのバリューの並び順通りに、TSVへ格納していきましたが、
JSONのメンバー(キーとバリューのセット)の並び順は関係ないとのこと。
つまり{"name":"john","gender":"m","age":"18"}
と{"name":"john","age":"18","gender":"m"}
は区別しません。
よって、今回はたまたま、JSONのメンバーの並び順通りでTSVに変換しても、問題ないですが、メンバーの並び順がバラバラのJSONだと、TSVに変換した時おかしくなってしまうことがわかりました。
なので、面倒ですが一度、ハッシュのキーを指定する形でバリューを取り出し、それをカラムの並び順通りに配列にいれてから、TSVへ入れ込むと、確実です。
require "json"
File.open("meibo.json") do |json|
File.open("meibo.txt", "w") do |txt|
array = JSON.load(json)
column = ["name","gender","age"]
txt.puts(column.join("\t"))
array.each do |line|
txt.puts(line.values.join("\t")) #JSONの各オブジェクトのバリューの並び通りにTSVへ入力
end
end
end
#TSVからJSONへ変換する方法
require "json"
require "csv"
hash_ary = []
File.open("meibo.json","w") do |json|
CSV.foreach("meibo.txt",col_sep: "\t", headers: true) do |line|
h = {name: line[0], gender: line[1], age: line[2]}
hash_ary << h
end
JSON.dump(hash_ary,json) #to_jsonを使用
end
まず、書き込みモードでJSONを書くためのファイルを用意します。
TSVファイルはCSVクラスでforeach
メソッドを使って各行をハッシュに変換していきます。変換したハッシュは一度配列(ここでは、hash_ary
)にいれて、全ての行が入れ終わったあとに、JSON.dump
でハッシュからJSONへの変換を行い、ファイルに書き込みます。
出力結果は以下の通り。
[{"name":"john","gender":"m","age":"18"},{"name":"paul","gender":"m","age":"20"},{"name":"alice","gender":"f","age":"15"},{"name":"dabid","gender":"m","age":"17"},{"name":"jasmin","gender":"f","age":"17"}]
###ハマったところ
require "json"
require "csv"
hash_ary = []
File.open("meibo.json","w") do |json|
CSV.foreach("meibo.txt",col_sep: "\t", headers: true) do |line|
h = {name: line[0], gender: line[1], age: line[2]}
hash_ary << h.to_json
end
json << hash_ary
end
最初は、上記のように書いてましたが、このようにすると。。
["{\"name\":\"john\",\"gender\":\"m\",\"age\":\"18\"}", "{\"name\":\"paul\",\"gender\":\"m\",\"age\":\"20\"}", "{\"name\":\"alice\",\"gender\":\"f\",\"age\":\"15\"}", "{\"name\":\"dabid\",\"gender\":\"m\",\"age\":\"17\"}", "{\"name\":\"jasmin\",\"gender\":\"f\",\"age\":\"17\"}"]
このように"
の前に\
が入ってうまく変換できませんでした。
to_json
メソッドで返ってきた文字列をそのままファイルに書き込むのではなく、JSON.dump
を使用したら、解決しました。