
キー名を間違えたJSONを取り込んだら、エラーも出ずに「変更なし」で終わった
自作の WordPress プラグインに、記事の内容を JSON で一括取り込みする機能がある。そこへキー名を1つ間違えた JSON を貼ったところ、エラーは出ず、保存は正常に終わり、結果は「変更なし」だった。
取り込み処理は壊れていなかった。知らないキーを読み飛ばす作りになっていて、読み飛ばした結果「変えるものが何もない」と正しく判定しただけだった。入力の間違いと、本当に変更がない場合とが、同じ表示に落ちていた。
何をする機能か
視聴順ガイドの1記事は、推奨順と時系列順の並び、作品ごとの注記、FAQ などでできている。これを管理画面の入力欄で1項目ずつ直すのは手間がかかるので、記事の中身を JSON で書き出し、直したものを貼り戻すと一括で反映される仕組みを作った。
取り込みは部分的でよい。投稿IDと、直したい箇所だけを入れた JSON でも通る。この「部分的でよい」が、今回の話の前提になる。
症状:貼ったのに何も変わらない
推奨順の中身を直した JSON を貼って保存した。渡したのは次の形だった。
{
"post_id": 123,
"entries_recommended": [ ... ]
}正しくは、こうだった。
{
"post_id": 123,
"orders": {
"recommended": {
"entries": [ ... ]
}
}
}起きたことは3つある。
エラーも警告も出ない
保存は正常に完了する
結果は「変更なし」で、1件も反映されていない
JSON としては正しく、投稿IDも合っている。入口の検査をすべて通過したうえで、何も起きなかった。
原因:知らないキーは読まれない
取り込み処理は、自分が知っているキーを順に取りに行く。あれば使い、なければその項目は「今回は触らない」と解釈する。部分的な JSON を受け付けるための作りで、これ自体は意図どおりだ。
entries_recommended というキーは、処理の側から見ると存在しないのと同じだった。読まれないまま捨てられ、残ったのは投稿IDだけの JSON になる。それは「直す項目が1つもない取り込み」として、正常に処理された。
前回の記事で、保存処理のルールを「送られてこなかったキーは、空で上書きしない。触らない」と決めた。このルールは今も正しいと思っている。ただしその裏側として、書き間違えたキーも「送られてこなかった」と同じ扱いになる。データを守るための既定動作が、入力ミスを見えなくしていた。
うるさく失敗する場所と、黙る場所
同じ取り込み処理には、はっきり止まる検査も入っている。
作品IDの集合が保存済みのものと食い違うと、1件も保存せずに中止する
投稿IDが編集中の記事と違う JSON は保存しない(貼り間違い対策)
リード文が上限の字数を超えると保存を拒否する
並べてみると、守っていたのは「データが壊れる方向」の間違いだけだった。「何も効かない方向」の間違いには、検査が1つも無かった。壊れないので後回しになり、後回しのまま忘れていた。
もう1つの「変更なし」
同じ表示に落ちる経路が、あとからもう1つ見つかった。区切り線のラベルだけを直した JSON だ。
視聴順の途中に入れる区切り線の行は、作品IDを持たない。取り込みは作品IDを軸に突き合わせるので、区切り線の行はその対象に入らない。ラベルだけを変えた JSON は「変更はありませんでした」で終わる。
こちらはキー名を間違えていない。正しい JSON でも、取り込みが対応していない項目は同じように消える。利用者から見れば、書き間違いと未対応は区別がつかない。
いまの対処
コードはまだ直していない。運用で止めている。
保存すると、どの項目を反映したかの一覧が出る。そこに、直したはずの項目名が出ているかを毎回見る。出ていなければ、表示が「変更なし」でも「成功」でも、その修正は入っていない。
コード側で足すなら、受け取ったキーと知っているキーの差分を取って、警告に出すだけで済む。
$unknown = array_diff( array_keys( $payload ), self::KNOWN_KEYS );
if ( $unknown ) {
$warnings[] = '未対応のキー: ' . implode( ', ', $unknown );
}保存せずに結果だけ確認できる dry-run も、課題として積んである。
持ち帰ったルール
「変更なし」を成功として読まない
直したつもりで貼って「変更なし」が返ってきたなら、それは失敗の表示だ。何も変えていないときにだけ、正常な結果になる。
部分更新を受け付けるなら、未知のキーは報告する
「無いキーは触らない」と「知らないキーは黙って捨てる」は、同じ実装から出てくる。前者だけが欲しいなら、後者には別に手当てが要る。
反映した項目を一覧で返す
今回、原因に早くたどり着けたのは、反映した項目の一覧を返す作りにしてあったからだった。「保存しました」だけを返す実装だったら、公開ページを見るまで気づけなかった。
こうした取り込みの仕組みを作っているのは、アニメシリーズの視聴順ガイドサイトを個人で運営しているためです。「どれから見ればいいのか分からない」シリーズの推奨順・時系列順・製作順をまとめています。
今回の事故が起きたのは、英語圏向けの同構成のサイト watchordr.com の作業中でした。共通のコードベースをフォークして展開しているので、同じ落とし穴は全サイトに同じ形で埋まっています。