Java .properties パーサー
.properties は最も単純に見える設定形式ですが、自作パーサーが最も間違えやすい形式でもあります。このページは java.util.Properties.load が実際に使う手順(JDK の LineReader、続いて load0 と loadConvert)をそのまま実行し、貼り付けたテキストがどんなマップになるかをキーごとに、また JSON と Properties.store が書き戻す形でも表示します。 区切り文字は =、: と空白です。したがって "c three" はキー c、値 three であり、"key with spaces=x" はキーが "key"、値が "with spaces=x" になります。区切り文字の前後の空白は消えますが、値の末尾の空白は残ります。行末のバックスラッシュは次の行を連結し、その行の字下げは取り除かれますが、バックスラッシュが偶数個なら継続しません。コメント行もバックスラッシュで終わっても継続しません。# と ! は行の最初の非空白文字のときだけコメントを開くので、URL の断片にある # は値に残ります。エスケープは \t \n \r \f と \uXXXX だけで、他のバックスラッシュは黙って消えます。 実務で最大の問題は文字コードです。ほぼすべてのフレームワークが呼ぶ load(InputStream) はバイト列を ISO-8859-1 として読むため、UTF-8 で保存したファイルは文字化けします。入力に非 ASCII が含まれるとき、このページは両方の読み方を並べて示し、変わってしまうキーを名指しします。 すべてブラウザ内で動き、アップロードは行いません。ただし重複したキーのどちらが意図だったか、おかしく見える値が本当に間違いかまでは分かりません。この道具が示すのは JDK がこのバイト列に何をするかであって、あなたが何を書きたかったかではありません。
一意なキー
11
代入の数
12
コメント行
1
継続した行
1
指摘
6
ほぼすべてのフレームワークの既定である load(InputStream) は常に ISO-8859-1 で読みます。二つの読み方を切り替えて、どのキーが変わるか確かめてください。
非ASCII文字が文字化け · 行 10
load(InputStream) はバイト列を ISO-8859-1 として読みます。UTF-8 で保存したファイルでは 1 文字が 2〜3 文字に化けます。\uXXXX で書くか、文字セットを指定した Reader を使ってください。
- greeting: "Grüße" → "GrüÃe"
空白が区切り文字 · 行 4
この行には = も : もありません。最初の空白やタブがキーと値を分けるので "c three" は c = three です。
app.mode
末尾の空白が残る · 行 9
区切り文字の前後の空白は捨てられますが、値の末尾の空白は値の一部です。エディタにも見えず、エラーにもなりません。
app.token (+3)
キーの中の区切り文字 · 行 6
このキーにはエスケープされた =、: または空白が含まれます。1 つのキーであり、最初の "=" で分割するコードは誤った位置で切ります。
menu.label 1
コメントではない · 行 14
# と ! が コメントを開くのは行頭の最初の非空白文字のときだけです。ここでは値の後ろにあるので値に含まれます。
note
重複したキー · 行 2, 12
同じキーが複数回指定されています。load は最後の値だけを残し、何も警告しません。前の行は生きて見えるだけの死んだ設定です。
app.name → "orders-api-v2"
| 行 | キー | 値 | 区切り | 備考 |
|---|---|---|---|---|
| 1 | # Spring Boot style configuration — and every trap in the format | コメント | ||
| 2 | app.name | orders-api | = | 上書き済み |
| 3 | server.port | 8080 | : | |
| 4 | app.mode | production | 空白 | 空白が区切り文字 |
| 5 | jdbc.url | jdbc:postgresql://db:5432/orders?ssl=true | = | |
| 6 | menu.label 1 | Save as… | = | キーの中の区切り文字 |
| 7–8 | welcome.message | Hello and welcome | = | |
| 9 | app.token | abc123··· | = | 末尾の空白が残る |
| 10 | greeting | Grüße | = | |
| 11 | greeting.escaped | Grüße | = | |
| 12 | app.name | orders-api-v2 | = | |
| 13 | path | C:\\Users\\dev\\app | = | |
| 14 | note | value # this is not a comment | = | コメントではない |
表では制御文字を \n や \t に置き換え、値末尾の空白を · で見えるようにしています。JSON ビューはそのままの値です。
使い方
- ファイルの内容を貼り付けるか、.properties ファイルをドロップ領域に落としてください。ブラウザ内で読むだけでアップロードはしません。
- 行ごとの表を見ます。各項目が何行目から来たか、実際に使われた区切り文字は何か、その行に変わった点があるかがバッジで示されます。
- 上の指摘を順に片付けます。重複キー、値末尾の空白、次の項目を飲み込んだ継続行の三つが、本番で静かに挙動を変えます。
- 非 ASCII があるときは UTF-8 と ISO-8859-1 の読み方を切り替え、load(InputStream) がアプリに何を渡すかを確認します。
- 設定の差分には JSON ビューを、書き戻すファイルには Properties.store ビューをコピーしてください。区切り文字も非 ASCII もすべてエスケープされます。
よくある質問
- "c three" に = がないのに、なぜキーと値に分かれるのですか。
- この形式では空白も区切り文字だからです。Properties.load はエスケープされていない最初の =、: または空白文字を探し、その手前をキーとします。そのため "c three" はキー c と値 three になり、"key with spaces=x" はキーが "key"、値が "with spaces=x" となって = は区切り文字になれません。キーに空白を入れたい場合は "\ " のようにエスケープする必要があり、Properties.store もそうしたキーを書き戻すときはまさにその形にします。
- 日本語やアクセント付き文字が化けます。何が起きていますか。
- java.util.Properties.load(InputStream) は、ストリームを ISO-8859-1 として 1 バイト 1 文字で読むと規定されています。UTF-8 で保存したファイルでは 1 文字が 2〜3 バイトであり、その各バイトがラテン文字になって化けます。例外も出ないので、壊れた文字列は表示まで届きます。対処は三つです。非 ASCII を \uXXXX で書く(native2ascii と Properties.store のやり方)、UTF-8 の InputStreamReader で load(Reader) を呼ぶ、または既定が UTF-8 の loadFromXML を使うことです。
- 値の末尾の空白は本当に残るのですか。
- 残ります。そしてそれが一群のバグ報告を生みます。行頭の空白、区切り文字の前後の空白、継続行の字下げはすべて捨てられますが、値の末尾の空白はそのままです。そこで行が終わり、誰も削らないからです。空白が一つ紛れたパスワードや URL は構文エラーなく解析され、接続だけが失敗し、エディタでもレビューでも正常なものと見分けがつきません。このページの表はその文字を目に見える形で示します。
- どんな行が次の行へ継続しますか。
- バックスラッシュが奇数個で終わるときです。最後のバックスラッシュが取り除かれ、次の行が連結され、その行の先頭の空白は削られます。偶数個なら継続せず、バックスラッシュは半分の個数の実文字として残ります。ここから二つの落とし穴が生まれます。値の末尾にバックスラッシュが紛れると、その下の "key=value" がその値に飲み込まれ、そのキーは消えます。またコメント行はバックスラッシュで終わっても継続しないので、次の行は普通の項目として解析されます。
- 同じキーを二行で指定したら、どちらが勝ちますか。
- 後の行が、何も言わずに勝ちます。Properties は Hashtable であり、load は項目ごとに put を呼ぶだけなので、後の行が前の行を上書きします。警告も例外も記録もありません。断片を集めてファイルを作ったときや、同じキーを一度は = で、一度は : で書いたときに、うっかり作りやすい状態です。このページは重複したキーと、それを指定したすべての行番号を並べ、負けた行を薄く表示します。
- .env ファイルと同じ形式ですか。
- 違います。同じだと思い込むことが、静かな障害のよくある原因です。.env には空白区切りがなく、引用符で値を保護したり複数行を収めたりし、値の後ろの # をインラインコメントとして扱い、${VAR} 展開に対応することも多いです。.properties にはそれらがありません。引用符は値に入る普通の文字で、値の後ろの # も値の一部、形式そのものに変数展開はなく(Spring が上に載せているだけです)、継続は引用符ではなくバックスラッシュで行います。
関連ツール
テキストエンコーディング変換
Shift_JIS・EUC-JP・Windows-1252 など非 UTF-8 のテキストを UTF-8 で読める形に。
Mbox アーカイブビューア
mbox アーカイブをブラウザーで分割。メッセージ境界、デコード済みヘッダー、MIME 構造、スレッド、重複 Message-ID を表示し、アップロードはしません。
Java クラスファイル解析ツール
コンパイル済みの .class ファイルをブラウザーで解析します。クラスファイルバージョンと Java リリース、アクセスフラグ、定数プール、フィールド、メソッド、参照クラスを表示します。
BSON デコーダー・Extended JSON ビューア
MongoDB の BSON ファイルをブラウザ内で解読します。要素型と型バイト、正確な int64 と decimal128、正準・緩和の Extended JSON を同時に表示します。
JSON to Java クラス(POJO)コンバーター
JSON オブジェクトを型マッピングフィールドを備えた Java POJO クラスにブラウザ上で変換します。
文字列エスケープ変換
1つの入力で10コンテキスト分のエスケープ出力 — JavaScript、JSON、HTMLエンティティ、URL、SQL、regex、Bash(シングル/ダブルクォート)、C文字列、Unicode \u。必要なものをコピー。