AIで作ったJSON商品を販売前に検査する|型違いと欠落3件を止めた

価格の1200と"1200"は、JSON では別の値です。見た目は近くても、前者は数値、後者は文字列として処理されます。
2026 年 10 月 1 日、AI で下書きしたプロンプト集の商品台帳を検査しました。初稿には、価格と公開状態の型違いが 2 件あります。さらに、台帳へ載せた CSV が商品フォルダにありません。最初の実行は 3 件で失敗し、修正後は 0 件になりました。
JSON として読めることと、販売物の台帳として正しいことは別です。今回は Python の標準機能だけで、その境界を確かめます。
前回完了時から変えたこと
前回は、CSV 商品のセルが数式として扱われる入口を調べました。危険な先頭文字を持つ 4 セルを直し、同じ検査で 0 件へ戻しました。今回は、複数ファイルを束ねる JSON 台帳へ対象を広げます。
| 時点 | 確認できること | 残る問題 |
|---|---|---|
| 前回完了時 | CSV内の数式候補を販売前に止めます | 商品台帳の型と同梱物は見ません |
| 今回完了後 | JSON台帳の必須項目、型、同梱ファイルを一度に検査します | 商品内容の価値と各ファイルの品質は別に確認します |
読み飛ばせる章: JSONの型と
pathlibを使った経験がある場合は、「3件の不備を入れた台帳を用意する」から進めます。
なぜJSONの構文検査だけでは足りないのか
JSON のパーサーは、波括弧や引用符が正しいかを調べます。しかし、価格を文字列で書いても構文は正しいままです。falseを"false"と書いた場合も、どちらも JSON として読み込めます。
販売用のプロンプト集では、台帳を次の処理へ渡す想定です。
price_yenを数値として価格表示へ使いますdraftを真偽値として販売対象から除外しますfilesに記録したファイルを ZIP へ梱包します
型が違うと、価格の比較や公開判定が崩れます。同梱ファイルがなければ、台帳だけ完成しても購入者へ渡す商品は欠けています。
私は、AI へ JSON を作らせた直後ではなく、販売候補を固める直前に検査を置きます。人がファイルを移動した後の欠落も拾うためです。
[AIでプロンプト集とJSON台帳を下書き]
│
▼
[人が価格、公開状態、同梱物を調整]
│
▼
[JSONを解析し、必須項目と型を検査]
│
▼
[filesの各パスを商品フォルダで確認]
┌──────┴──────┐
▼ ▼
errors=0 errors>0
│ └── 台帳か同梱物を修正
▼
[人が内容を確認]
│
▼
[販売候補ZIP]
検査が通っても、自動販売はしません。プロンプトの内容、CSV の列、価格の妥当性は、人が別に確認します。
用語をそろえる
| 用語 | この回での意味 |
|---|---|
| JSON | 文字列、数値、真偽値、配列、オブジェクトなどを表せるデータ形式です |
| 商品台帳 | 商品ID、価格、同梱ファイル、公開状態を記録するJSONです |
| 型 | 文字列、整数、真偽値、配列など、値の扱いを決める種類です |
| manifest | 商品に含める項目とファイルを列挙した台帳です |
| product root | 購入者へ渡すファイルを置いた商品フォルダの基準位置です |
| path traversal | ../などを使い、商品フォルダの外を指すパスです |
| 終了コード | 合格を0、不合格を0以外で伝える値です |
3件の不備を入れた台帳を用意する
実験場所を作り、Python の版を確認しました。
mkdir -p hibanas-json-product-lab/product/prompts
cd hibanas-json-product-lab
python3 --version
Python 3.13.5
商品フォルダには、README とプロンプトを 1 件ずつ置きます。台帳が要求する CSV は、まだ作りません。
product/README.mdは次の内容です。
# 顧客ヒアリング用AIプロンプト集
顧客の課題、予算、完了条件を確認するためのテンプレートです。
product/prompts/interview.mdも保存しました。
# ヒアリングプロンプト
顧客が困っている作業を、頻度と所要時間を含めて質問してください。
初稿のmanifest-unsafe.jsonには、意図的に 3 件の不備を残します。
{
"product_id": "prompt-pack-01",
"title": "顧客ヒアリング用AIプロンプト集",
"price_yen": "1200",
"files": [
"README.md",
"prompts/interview.md",
"assets/checklist.csv"
],
"draft": "false"
}
price_yenとdraftは引用符で囲まれています。そのため、どちらも文字列です。assets/checklist.csvは台帳にありますが、商品フォルダには存在しません。
Python 自身の JSON コマンドで構文だけを確認しました。
python3 -m json manifest-unsafe.json >/dev/null
printf 'exit=%s\n' "$?"
exit=0
構文検査は通りました。この結果だけでは、2 件の型違いと 1 件のファイル欠落を見つけられません。
Python標準機能で台帳と実ファイルを照合する
検査コードをcheck_manifest.pyとして保存しました。追加パッケージは使いません。
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
REQUIRED = {
"product_id": str,
"title": str,
"price_yen": int,
"files": list,
"draft": bool,
}
def validate(data: Any, product_root: Path) -> list[str]:
errors: list[str] = []
if not isinstance(data, dict):
return ["TYPE root expected=object"]
for key, expected_type in REQUIRED.items():
if key not in data:
errors.append(f"MISSING key={key}")
elif type(data[key]) is not expected_type:
errors.append(
f"TYPE key={key} expected={expected_type.__name__} "
f"actual={type(data[key]).__name__}"
)
if type(data.get("price_yen")) is int and data["price_yen"] <= 0:
errors.append("VALUE key=price_yen expected=positive_integer")
files = data.get("files")
if isinstance(files, list):
for index, item in enumerate(files):
if not isinstance(item, str):
errors.append(f"TYPE key=files[{index}] expected=str")
continue
target = (product_root / item).resolve()
try:
target.relative_to(product_root.resolve())
except ValueError:
errors.append(f"OUTSIDE key=files[{index}] path={item}")
continue
if not target.is_file():
errors.append(f"MISSING_FILE key=files[{index}] path={item}")
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("manifest", type=Path)
parser.add_argument("product_root", type=Path)
args = parser.parse_args()
try:
data = json.loads(args.manifest.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
print(f"PARSE {error}")
return 1
errors = validate(data, args.product_root)
for error in errors:
print(error)
print(f"result={'FAIL' if errors else 'PASS'} errors={len(errors)}")
return 1 if errors else 0
if __name__ == "__main__":
raise SystemExit(main())
json.loads()は JSON を Python の値へ変換します。そこで終わらず、5 項目の存在と型を照合します。
type(value) is expected_typeと書いたのは、Python ではboolがintの派生型だからです。isinstance(False, int)は真になります。価格へfalseが入った台帳を整数として通さないため、型を厳密に比べました。
ファイルの確認では、resolve()でパスを解決します。その後にrelative_to()を使い、商品フォルダの外へ出るパスをOUTSIDEとして止めます。最後にis_file()で実在する通常ファイルかを調べます。
初稿を検査して3件で止める
初稿の台帳と商品フォルダを渡し、終了コードも表示しました。
python3 check_manifest.py manifest-unsafe.json product
printf 'exit=%s\n' "$?"
TYPE key=price_yen expected=int actual=str
TYPE key=draft expected=bool actual=str
MISSING_FILE key=files[2] path=assets/checklist.csv
result=FAIL errors=3
exit=1
不備は 3 件です。価格の型、公開状態の型、CSV の欠落が別々に出ました。
ここでfilesの検査を外せば、エラーは 2 件に減ります。しかし、購入者へ渡す CSV は戻りません。数を減らすのではなく、台帳と商品を同じ状態へそろえます。
型を直し、欠けたCSVを追加する
修正版のmanifest-safe.jsonでは、価格を整数、公開状態を真偽値に変えました。ファイル一覧は変えません。
{
"product_id": "prompt-pack-01",
"title": "顧客ヒアリング用AIプロンプト集",
"price_yen": 1200,
"files": [
"README.md",
"prompts/interview.md",
"assets/checklist.csv"
],
"draft": false
}
不足していたproduct/assets/checklist.csvも追加します。
item,status
課題を1行で記入,未確認
完了条件を記入,未確認
初稿と同じ検査コードへ修正版を渡しました。
python3 check_manifest.py manifest-safe.json product
printf 'exit=%s\n' "$?"
result=PASS errors=0
exit=0
検査条件は変えていません。台帳の 2 項目と商品フォルダの 1 ファイルを直し、errors=3からerrors=0へ変わりました。
検査した版をハッシュで固定する
初稿、修正版、検査コード、追加した CSV の SHA-256 を記録しました。
sha256sum manifest-unsafe.json manifest-safe.json check_manifest.py product/assets/checklist.csv
fad0e3ed0b0d00123d5c4f8dd0e3c2794b3a9df7835e41694381a3263459196c manifest-unsafe.json
b00b9ea26d804a9e217784178dd61399c8702975b3a15273ed562ea540e79167 manifest-safe.json
ee4e3e8632155855efb6e5e1281092bda30f60472e6d513be5db5aed297d8cfb check_manifest.py
ce8778fc2755dd9496ebb1ce768624ebde9ed0aa5744362e8fcb8bad1efd2bd9 product/assets/checklist.csv
ハッシュは、検査したファイルと後で見るファイルが同じかを確かめる値です。商品内容の正しさや安全性までは証明しません。1 文字変えれば値も変わるため、再検査と組み合わせます。
0件でも商品価値は判定できない
今回のPASSが示すのは、決めた 5 項目の型が合い、台帳に載せた 3 ファイルが商品フォルダにあることです。プロンプトの質問が顧客ヒアリングで役立つかは分かりません。CSV の列が足りるかも未検査です。
独自コードの規則は小さく保ちました。商品項目が増える場合は、JSON Schema などへ移す方が管理しやすくなります。ただし、Schema で型を検査しても、ローカルファイルの実在確認は別に必要です。
| 検査 | 分かること | 分からないこと |
|---|---|---|
json.loads() | JSONを解析できるか | 商品に必要な項目かは分かりません |
| 型の照合 | 価格や公開状態を意図した型で扱えるか | 値の事業上の妥当性は分かりません |
relative_to() | 商品フォルダの外を指していないか | ファイル内容は分かりません |
is_file() | 台帳のパスに通常ファイルがあるか | ZIPへ実際に入ったかは分かりません |
| 人の確認 | 内容、価格、操作順が販売候補に合うか | 将来の更新ミスは自動で止まりません |
私は、この検査の後に ZIP を作り、展開したフォルダも同じ条件で検査します。作業場所で存在しても、梱包設定から漏れる場合があるためです。
よくあるエラー
| 症状 | 原因 | 対応 |
|---|---|---|
PARSEが出ます | カンマ、引用符、波括弧の構文が崩れています | 表示された行と列を直し、再実行します |
TYPE key=price_yenが出ます | 1200を"1200"のような文字列で保存しています | 引用符を外し、正の整数へ直します |
TYPE key=draftが出ます | falseを"false"として保存しています | JSONの真偽値trueかfalseを使います |
MISSING_FILEが出ます | 台帳のパスと商品フォルダが一致しません | ファイルを追加するか、台帳のパスを直します |
OUTSIDEが出ます | ../などで商品フォルダの外を指しています | 必要なファイルを商品内へ移します |
| 手元ではPASSでもZIPにファイルがありません | 梱包後のフォルダを検査していません | ZIPを展開し、展開先をproduct_rootへ渡します |
errors=0でも商品が使えません | 内容と操作順は検査対象外です | 実際の利用手順を人が通します |
失敗結果を残すと、直した場所が分かる
初稿のerrors=3は、AI が JSON の構文を壊した結果ではありません。構文だけなら正しく書けていました。問題は、後続処理が求める型と、購入者へ渡す実ファイルにあります。
価格と公開状態は、引用符を外すだけで直りました。CSV は、台帳から消さずに商品へ追加しました。チェック表が必要という商品設計を残したまま、不足を埋めるためです。
AI でデジタル商品を作ると、説明文、台帳、同梱物を別々の会話で増やしがちです。文章が完成しても、3 つの境界は自然には一致しません。販売前に完成したフォルダを入力へ渡し、型とファイルを同じ実行で確認すると、見た目だけ整った商品を止められます。
次に追加するなら、ZIP 展開後の再検査です。今回の小さな検査を捨てず、入力するproduct_rootだけを完成品へ変えます。
一次情報
| 資料 | 確認した内容 |
|---|---|
| RFC 8259: The JavaScript Object Notation Data Interchange Format | JSONが文字列、数値、真偽値、配列、オブジェクトなどを区別する仕様 |
| Python Documentation: json | json.loads()による解析、JSONDecodeError、コマンドライン検査 |
| Python Documentation: pathlib | Path.resolve()、relative_to()、is_file()によるパスと実ファイルの確認 |


