CSVからJSONへ: クォート、型、エンコーディングの罠
CSVは実際にパースするまでは単純に見えます。クォートのルール、すべてが文字列になる問題、フィールド内の改行、そして整然と見えるスプレッドシートを壊れたJSONに変えてしまうエンコーディングの問題を扱います。
CSVは誰もが単純だと思い込む形式です。値をカンマで区切った行を カンマで分割すれば終わり、という具合です。ところがあるフィールドに カンマが入ったり、クォートが入ったり、改行が入ったり、意味のある 先頭のゼロが入ったりすると、単純な分割は崩れます。CSVをJSONに 変換すると、こうしたエッジケースがすべて表面化します。JSONには 型と構造がありますが、CSVにはテキストしかないからです。実際に 問題になるのは次の点です。
CSVは標準と呼べるものではありません
CSVを説明するRFC(4180)はありますが、実際のファイルはそれより
前から存在し、これを自由に無視します。区切り文字はカンマかも
しれませんし、セミコロン(カンマが小数点の区切りであるロケールで
よく見られます)かもしれませんし、タブかもしれません。改行は
\nかもしれませんし\r\nかもしれません。ヘッダー行はあるかも
しれませんしないかもしれません。CSVをパースする際の第一のルールは、
何も仮定できないということです。あるエクスポートで動くパーサーが
次のファイルでは壊れます。だから「とりあえずカンマで分割する」という
方法は、難しい部分にたどり着く前にすでに間違っているのです。
クォートが中心となるルールです
CSVの中で明確に定義されている唯一の部分は、クォートがどのように 特殊文字を救い出すかです。二重引用符で囲まれたフィールドは、カンマ、 改行、クォートを含むことができます。
name,note
"Smith, John","He said ""hi"" twice"
これは2つのフィールドです。Smith, Johnと
He said "hi" twiceです。クォート内のカンマは区切り文字ではなく
データであり、二重になったクォート""はエスケープされた1つの
クォートです。単純なsplit(",")はこれを壊れた4つの断片に
してしまいます。正しいCSVからJSONへの変換であればクォートの
ルールを守る必要があり、これこそが一行のコードではなく本物の
パーサーを選ぶ理由です。
すべてが文字列です
これが2つの形式の間にある概念上のギャップです。CSVには型が ありません。すべてのセルがテキストです。JSONには数値、真偽値、 nullがあります。そのため変換は決断を迫ります。すべてを文字列の まま保つのか、それとも型を推論するのか、ということです。
型の推論は便利でありながら危険です。
007が7になります。すると郵便番号、製品SKU、あるいは電話番号の 先頭のゼロを壊してしまったことになります。1.0が1になったり、さらに悪いことに長い小数が正確に表現できない 浮動小数点数になったりします。TRUE/FALSEがツールによって真偽値になったり、文字列のまま 残ったりします。- 空のセルが
""になったり、nullになったり、まったく省略されたり します。
普遍的に正しい答えはありません。データ交換が目的なら、すべてを 文字列のまま保ち、利用する側が意図的にキャストするようにするのが 安全な既定値です。自分で管理する分析が目的なら型の推論でも 構いません。ただし、変換ツールがどちらを行っているかは知っておく 必要があります。静かな型推論は「データは問題なく見えたのに インポートが間違っていた」という典型的な原因だからです。
フィールド内の改行は行ベースのツールを壊します
クォートで囲まれたフィールドは実際の改行を含むことができるため、
**CSVのレコードはファイルの1行と同じではありません。**CSVを
1行ずつ処理するツール、たとえばwc -l、単純なreadlineループ、
ログのパイプラインなどは、複数行にまたがるフィールドを持つレコードを
誤って数え、分割してしまいます。行数が多すぎるように見える場合は、
クォートされたフィールド内の改行を最初に疑うべきです。
エンコーディングとBOM
CSVはエンコーディングの宣言を持たないため、バイト列は曖昧です。 繰り返し発生する2つの問題があります。
- **BOM。**スプレッドシートはファイルの先頭にバイトオーダーマークを
付けたUTF-8として保存することがよくあります。すると最初の
ヘッダー名が静かに
nameではなくnameになり、"name"で 検索すると目に見える理由もなく失敗します。読み込む際にBOMを 取り除いてください。 - **UTF-8以外のエクスポート。**Latin-1や地域別のコードページとして 保存されたファイルは、UTF-8として読み込むとアクセント付きの文字が 文字化けに変わります。これを示すフラグはないので、元の エンコーディングを知っているか検出する必要があります。
短いチェックリスト
CSVからJSONへの変換が誤った出力を生む場合は、次の項目を順番に 確認してください。
- 区切り文字は正しいですか?(カンマ対セミコロン対タブ)
- クォートのルールを守っていますか?二重になったクォートと フィールド内のカンマを含めて、です。
- ヘッダー行があり、BOMで汚染されていませんか?
- 型は推論したものですか、それとも文字列のまま保ったものですか? そしてそれは望んだものですか?
- エンコーディングは実際にUTF-8ですか?
クォートを正しく処理し、型について明確に選択してファイルを変換する には、当サイトのCSV ↔ JSON変換ツールが ブラウザ内で処理するため、データはローカルにとどまります。JSONに 変換した後、ダウンストリームに渡す前に形を強制したい場合は、 JSON Schemaで検証するが 次のステップです。また、表形式のデータではなく設定用の形式を 選んでいる場合は、JSON対YAMLがその トレードオフを扱います。