JSONの改行エスケープ問題をbody.md分離で解決した話|AI編集が確実に動くようになった
結論:body.mdに分離することで根本解決しました
JSONにMarkdown本文を書いていてAIの差分修正が失敗し続ける場合、解決策は本文をbody.mdとして分離することです。
VSCodeの正規表現置換で一時対処する方法もありますが、根本原因はJSONのエスケープ仕様にあります。分離することでその問題ごとなくなります。この記事では詰まったポイントと、実際にどう実装したかを整理します。
---
JSONに本文を書くとどんな問題が起きるか
JSONは文字列の中に改行を含められません。代わりに \n というエスケープ文字を使います。Markdownの本文をJSONのフィールドとして持つと、すべての改行が \n に変換された状態で保存されます。
たとえばこういう状態になります。
{
"body": "## 見出し\n\n本文の1行目です。\n\n本文の2行目です。"
}見た目は問題なく見えますが、AIツール(Claude等)でこのファイルを差分修正しようとすると困ったことが起きます。
AIが「この文字列を探してこれに置き換えて」という指示(edit_fileツール)を出すとき、oldTextとしてファイルの実際の内容を指定する必要があります。しかしJSONの中の \n は、ファイルビューアやAIが読み取るときに解釈が異なる場合があり、oldTextが一致しないというエラーが頻発します。
Error: Could not find exact match for editこのエラーを見たことがある方は、同じ状況にいるかもしれません。
---
AIの差分修正が失敗し続けた詰まりポイント
私がこの問題に気づいたのは、Claudeに記事の一部を修正してもらおうとしたときです。
本文の一段落を「この表現を変えてほしい」と依頼すると、Claudeはedit_fileツールでoldTextを指定して差分修正を試みます。しかし何度試しても「exact match が見つからない」というエラーで失敗し続けました。
原因を探ると、以下の構造が問題でした。
- ファイル上のJSONでは改行が
\nとして保存されている - AIがoldTextを指定するとき、実際の改行文字(LF)として書こうとする
- この2つが一致しないためマッチに失敗する
さらに日本語の文字列が絡むと、エスケープ処理の解釈がより複雑になります。\" や \\ が混在する場合も同様です。
結果として「差分修正できないなら全文書き直しで」という運用になり、クレジットを無駄に消費する悪循環に陥っていました。
---
VSCodeの正規表現置換で一時対処する方法
既存のJSONファイルから本文を取り出したい場合、VSCodeの正規表現置換が使えます。
手順:
- VSCodeでJSONファイルを開く
Ctrl+Hで検索・置換パネルを開く- 右上の
.*アイコン(正規表現モード)をONにする - 検索欄に
\\nと入力する - 置換欄で
Ctrl+Enterを押して実際の改行文字を入力する - 「すべて置換」を実行する
これでJSON内の \n が実際の改行に変換されます。変換後の本文をコピーしてbody.mdに貼り付けることができます。
注意点:
\\nと\nの違いに気をつけてください。正規表現モードでは\\nが\nという文字列にマッチします- JSONとして保存し直す場合は再度エスケープが必要になります
- これは一時対処です。根本解決にはなりません
---
body.mdを分離する設計とその考え方
根本解決は、メタデータ(article.json)と本文(body.md)を別ファイルに分けることです。
変更前:
articles/my-article/
└── article.json ← メタデータ + body(全部入り)変更後:
articles/my-article/
├── article.json ← メタデータのみ
├── body.md ← 本文Markdownのみ
└── eyecatch.svgarticle.jsonにはtitle・slug・publishedAt・categorySlug等のメタデータだけを持たせます。bodyフィールドは削除します。
{
"title": "記事タイトル",
"slug": "my-article",
"publishedAt": "2026-04-25T09:00:00.000Z",
"categorySlug": "practice-log",
"topicSlugs": ["tag1", "tag2"],
"seoTitle": "SEOタイトル",
"seoDescription": "説明文",
"seoKeywords": "キーワード1,キーワード2"
}body.mdには純粋なMarkdownを書きます。JSONエスケープは一切不要です。" も \n も気にせずそのまま書けます。
この設計にすることで、AIがbody.mdを差分修正するときにJSONエスケープの問題が発生しなくなります。Markdownファイルはテキストファイルなので、oldTextがそのまま一致します。
---
post.mjsへの実装手順(数行の追記で対応)
投稿スクリプト(post.mjs)にbody.mdを読み込む処理を追加します。以下の数行を追記するだけです。
既存コードでarticle.jsonを読み込んでいる箇所の直後に追記します。
const article = JSON.parse(fs.readFileSync(articlePath, 'utf-8'))
// body.mdが存在すれば article.body を上書き(分離管理対応)
const bodyMdPath = path.join(articleDir, 'body.md')
if (fs.existsSync(bodyMdPath)) {
article.body = fs.readFileSync(bodyMdPath, 'utf-8')
console.log(`📝 body.md を読み込みました`)
}この追記により、body.mdが存在する記事はbody.mdの内容が本文として使われます。body.mdがない記事はarticle.json内のbodyフィールドが従来通り使われます。後方互換性があるため、既存記事への影響もありません。
既存記事の移行手順:
- 該当フォルダにbody.mdを新規作成する
- article.jsonのbodyフィールドの値をコピーする
- 上記のVSCode正規表現置換で
\nを実際の改行に変換する - body.mdに貼り付けて保存する
- article.jsonのbodyフィールドを削除する
移行後は、AIに「body.mdを差分修正してください」と依頼するだけで、エスケープ問題なく編集できるようになります。
---
この後どうなったか(継続記録)
body.mdへの分離後、AIによる記事の差分修正が安定して動くようになりました。「exact match が見つからない」というエラーはほぼ出なくなっています。
新規記事はbody.mdで書く運用に統一し、記事ごとのフォルダ構成もシンプルになりました。記事本文の管理としても、Markdownファイルをそのまま開いて読めるので確認しやすくなっています。
課題として残っているのは、既存記事のbody.mdへの移行です。記事数が増えるほど手作業のコストがかかります。現時点では新規記事から順次対応しています。
---
よくある質問
Q:body.mdのMarkdownはどんな記法が使えますか
## 見出し・**太字**・箇条書き(-)・番号付きリスト(1.)・コードブロック(\\\`)がそのまま使えます。JSONエスケープが不要なので、ダブルクォートや改行を気にせず書けます。
Q:article.jsonにbodyを残したままbody.mdも作るとどうなりますか
post.mjsの実装では、body.mdが存在する場合にarticle.jsonのbodyフィールドを上書きします。つまりbody.mdが優先されます。移行期間中は両方あっても動作しますが、混乱を避けるためにarticle.jsonのbodyは削除することをお勧めします。
Q:AIによる差分修正の安定性はどのくらい改善しましたか
体感では大幅に改善しました。修正前は10回中5〜6回程度エラーが出ていましたが、body.md分離後はほぼエラーが出なくなっています。ただし現時点では正確な計測はしていません。
---
まとめ
JSONに本文を埋め込む設計は、AIによる差分修正と相性が悪いです。\n エスケープの解釈の違いがoldTextのマッチ失敗を引き起こします。
解決策はシンプルで、body.mdとして本文を分離することです。post.mjsへの数行の追記と、VSCodeの正規表現置換を使った移行作業で対応できます。
今日できる一歩は、次に作る記事からbody.mdで本文を管理することです。それだけでAIとの編集作業が安定します。
この記事のファイル設計の判断は、何をどう分けるかという構造設計の考え方とつながっています。設計の判断軸を体系的に整理したい方は、bizinets.biz の判断軸ページが参考になると思います。判断軸について
bizinets.biz
記事を読んで「もっと知りたい」と思ったあなたへ
bizinets.biz では、ビジネスの仕組みづくりを体系的に学べるコンテンツを用意しています。まずは一度、覗いてみてください。
bizinets.biz へ行く →