階層構造をもつJSONのデータをCSVファイルの1行として保存するプログラムのメモ. ネットでは配列と同じような構造のJSONデータをCSVファイルに変換する例はよく見るけど, 入れ子になってるデータでもCSVファイルに保存したい.
- 階層構造をもつJSONのデータをCSVファイルの1行として保存する
- 保存したCSVファイルから逆にJSONに戻せるようにする
- 構造は未知としてライブラリ化する
作戦は,クラスオブジェクトをJSON化して,JSONのkeyはCSVのヘッダに格納して,valueは対応する列のセルに格納する. 階層構造を保つ場合は,key1:key2のように階層構造の親子関係を「:」で接続して表現する.
想定するJSONの階層構造例
想定するJSONはこれ.中括弧が3重になっていることから分かるように3階層になっている.
{"a":"aho","b":{"a":1,"b":2,"c":{"c":2,"d":3}}}
これをこんな感じのCSVに変換したい.ここではオブジェクト1個分だからCSVファイルではヘッダ1行とデータ1行.
#a,b:a,b:b,b:c:c,b:c:d aho,1,2,2,3
使い方
// JSONデータ(入力方法は省略)
// {"a":"aho","b":{"a":1,"b":2,"c":{"c":2,"d":3}}}
string json_txt = .... ;
// JSON -> CSV
// JSONからCSVのヘッダとデータ1行に変換
string csv_header = JsonCsv.JsonToCsvHeader(json_txt);
string csv_row = JsonCsv.JsonToCsvRow(json_txt);
Console.WriteLine(csv_header+csv_row);
// CSV -> JSON
// CSVヘッダからDictionary<string, object>に変換(key情報のみ格納)
var dict = JsonCsv.CsvHeaderToDict(csv_header+'\n');
// Dictionary<string, object>からJSONに変換(途中結果の確認用)
string json_dict = JsonCsv.DictToJson(dict);
// key情報とvalue情報をあわせてJSONに変換
string json_out = JsonCsv.CsvRowToJson(dict, csv_row+'\n');
Console.WriteLine(json_dict);
Console.WriteLine(json_out);
{"a":"aho","b":{"a":1,"b":2,"c":{"c":2,"d":3}}}
JSONからCSV
JSONからCSVヘッダ
階層構造を処理するためにDictionary<string, object>を用いて, objectにDictionary<string, object>を代入することでツリー構造を作る. ツリー構造を巡回するために再帰関数extractHeaderをJsonToCsvHeaderから呼び出す仕組み.
public static string JsonToCsvHeader(string json_txt){
Dictionary<string, object> dic = Json.Deserialize(json_txt) as Dictionary<string, object>;
string csv_txt = "#";
csv_txt += extractHeader("", (Dictionary<string, object>)dic);
return csv_txt + '\n';
}
// #a,b:a,b:b,b:b:c:c,b:c:d
static string extractHeader(string parent, Dictionary<string, object> dic){
string csv_txt = "";
bool is_first = true;
foreach(KeyValuePair<string, object> pair in dic)
{
//最初以外はカンマで区切る
if(!is_first){
csv_txt += ",";
}
is_first = false;
Type type = pair.Value.GetType();
if( type == typeof(Dictionary<string, object>) )
{
csv_txt += extractHeader(parent + pair.Key + ':', (Dictionary<string, object>)pair.Value);
}
else{
csv_txt += (parent + pair.Key);
Debug.Log(parent + pair.Key);
}
}
return csv_txt;
}
JSONからCSVデータ
public static string JsonToCsvRow(string json_txt){
Dictionary<string, object> dic = Json.Deserialize(json_txt) as Dictionary<string, object>;
string csv_txt = extractRow((Dictionary<string, object>)dic);
return csv_txt + '\n';
}
static string extractRow(Dictionary<string, object> dic){
string csv_txt = "";
bool is_first = true;
foreach(KeyValuePair<string, object> pair in dic)
{
//最初以外はカンマで区切る
if(!is_first){
csv_txt += ",";
}
is_first = false;
Type type = pair.Value.GetType();
if( type == typeof(Dictionary<string, object>) )
{
csv_txt += extractRow((Dictionary<string, object>)pair.Value);
}
else{
csv_txt += pair.Value;
}
}
return csv_txt;
}
CSVからJSON
CSVのヘッダをDictionaryに格納
ここでもDictionaryを使ってツリー構造を構築する. CSVのヘッダからDictionary<string, object>に一旦変換する. addDictItem関数は再帰処理の関数.
public static Dictionary<string, object> CsvHeaderToDict(string csv_txt)
{
var rows = csv_txt.Split('\n');
string[] header = rows[0].Split(',');
header[0] = header[0].Trim('#');
Dictionary<string, object> dict = new Dictionary<string, object>();
for(int i = 0; i<header.Length; i++)
{
var keys = header[i].Split(':');
addDictItem(keys, 0, ref dict);
}
return dict;
}
static void addDictItem(string [] keys, int idx, ref Dictionary<string, object> dict){
//2つ以上のkey
if(keys.Length > idx+1)
{
//追加
if(dict.ContainsKey(keys[idx])){
var child = (Dictionary<string, object>) dict[ keys[idx] ];
addDictItem(keys, idx+1, ref child);
}
//新規
else{
var child = new Dictionary<string, object>();
dict[ keys[idx] ]= child;
addDictItem(keys, idx+1, ref child);
}
}
//1つのkey
else{
dict[ keys[idx] ]="";
}
}
CSVのデータとDictionaryからJSONに変換
一旦Dictionaryが出来てしまえば簡単. CSVのデータ1行(csv_row)とヘッダ情報を格納したDictionaryとでJSON形式のデータを復元する.
public static string CsvRowToJson(Dictionary<string, object> dic, string csv_row)
{
csv_row = csv_row.Trim();
var cols = csv_row.Split(',');
int i_col = 0;
string json_txt = "{";
bool is_first = true;
foreach(KeyValuePair<string, object> pair in dic)
{
//最初以外はカンマで区切る
if(!is_first){
json_txt += ",";
}
is_first = false;
//子ノードを持っているとき
Type type = pair.Value.GetType();
if( type == typeof(Dictionary<string, object>) )
{
string pattern = "[^,]*,";
Regex regex = new Regex(pattern);
int max_count = i_col;
string row_next = regex.Replace(csv_row, "", max_count);
json_txt += "\"" + pair.Key + "\":" + CsvRowToJson((Dictionary<string, object>)pair.Value, row_next);
}
//子ノードが無いとき
else{
json_txt += "\"" + pair.Key + "\":\"" + cols[i_col] + "\"";
}
i_col++;
}
return json_txt + '}';
}