メインコンテンツへスキップ
見出し画像

キー名を間違えた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 の作業中でした。共通のコードベースをフォークして展開しているので、同じ落とし穴は全サイトに同じ形で埋まっています。

     
     
    アニメの視聴順ガイドを個人で3サイト運営。AniList API と Claude API を組み合わせて、記事の生成から公開までを回しています。設計・失敗・実測コストをそのまま置いていく場所。WordPress自作プラグイン/SEO実験。aniorder.com

    あなたへのおすすめ