壊れたJSONを修正してくれる「json_repair」を試す
ここで知った
GitHubレポジトリ
このシンプルなパッケージは、無効なjson文字列を修正するために使用できます。このパッケージが動作するすべてのケースを知るには、ユニットテストを確認してください。
https://github.com/josdejong/jsonrepair にインスパイアされています
##デモ
このライブラリがあなたの特定の問題を解決できるかどうか不明な場合、または単にオンラインでjsonを検証したい場合は、GitHubページのデモサイト( https://mangiucugna.github.io/json_repair/ )にアクセスしてください。
モチベーション
LLMに整ったJSONデータを返させるとなると、LLMは少し不安定になります。時には括弧を省略し、時にはそこにいくつかの単語を追加します。幸いにも、LLMが犯すミスはコンテンツを破壊することなく修正できるほど単純なものです。
この問題を確実に修正できる軽量なPythonパッケージを探しましたが、見つかりませんでした。
そこで、自分で作成しました
GPT-4o Structure Outputによって、このライブラリは時代遅れになるのではないでしょうか?
私たちの仕事の一部として、OpenAIのAPIを使用していますが、構造化された出力でも、結果が完全に有効なJSONではない場合があることに気づきました。そのため、私たちは今でもこのライブラリを使用して、そのような例外に対応しています。
サポートされるユースケース
JSONの構文エラーの修正
- 引用符の欠落、カンマの誤った位置、エスケープされていない文字、不完全なキーと値のペア。
- 引用符の欠落、不適切なフォーマットの値(true、false、null)、破損したキーと値の構造の修復。
不正なJSON配列とオブジェクトの修復
- 必要な要素(カンマ、括弧など)やデフォルト値(null、"")を追加することで、不完全または破損した配列/オブジェクトを修復します。
- このライブラリは、コメントや不適切な位置に配置された文字などの余分な非JSON文字を含むJSONを処理することができ、有効な構造を維持しながらそれらをクリーンアップします。
欠落したJSON値の自動補完
- 妥当なデフォルト値(空文字列やnullなど)で、JSONフィールド内の欠落した値を自動的に補完し、有効性を確保します。
なるほど、Structured Outputでもダメなケースはまだまだあるのか。あとローカルLLMとかでも役に立ちそうな気がする。
Colaboratoryで。
パッケージインストール
!pip install json-repair
で、json_repairを使う前に、まずは自分でJSONスキーマをPydanticオブジェクトでバリデーションするサンプル。
from pydantic import BaseModel, ValidationError
from typing import List, Optional
import json
# Pydanticモデルの定義
class UserModel(BaseModel):
name: str
age: int
email: Optional[str] = None
hobbies: List[str]
# JSONバリデーション関数
def validate_json(json_str: str):
try:
json_data = json.loads(json_str)
item = UserModel(**json_data)
print("Validation Success!\n")
print(json.dumps(item.dict(), indent=2, ensure_ascii=False))
except Exception as e:
if isinstance(e, json.JSONDecodeError):
print(f"JSONDecodeError: {e.msg}\n")
print(f"Error at line {e.lineno}, column {e.colno}")
elif isinstance(e, ValidationError):
print("Validation Error!\n")
print(json.dumps(json.loads(e.json()), indent=2, ensure_ascii=False))
else:
print(f"Unexpected error: {str(e)}")
まず正しいJSONで試してみる。
json_str = """
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"]
}
"""
validate_json(json_str)
Validation Success!
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": [
"読書",
"ゲーム"
]
}
キーが1つ足りないJSONでも試してみる。
# nameがない
json_str = """
{
"age": 30,
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"]
}
"""
validate_json(json_str)
Validation Error!
[
{
"type": "missing",
"loc": [
"name"
],
"msg": "Field required",
"input": {
"age": 30,
"email": "yamada@example.com",
"hobbies": [
"読書",
"ゲーム"
]
},
"url": "https://errors.pydantic.dev/2.9/v/missing"
}
]
文法的に破綻したJSON
# ケツカンマ
json_str = """
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"],
}
"""
validate_json(json_str)
JSONDecodeError: Expecting ',' delimiter
Error at line 5, column 5
こんな感じで、
- スキーマに合致しているかはPydanticでバリデーションする
- JSON自体が破綻しているかどうかは
json.loads
の例外を拾う
ことはできるけど、エラーを拾えるというところまでになる。
json_repairを使うとどうやら後者については自動的に修正してくれる様子。ユースケースを見るともうちょっと踏み込んだ感じもできるように思えるけど、一旦は思いつくところでいろいろ試してみる。
その1: ケツカンマ
from json_repair import repair_json
bad_json_str = """
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"],
}
"""
good_json_string = repair_json(bad_json_str)
validate_json(good_json_string)
Validation Success!
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": [
"読書",
"ゲーム"
]
}
その2: 途中のカンマ抜け
from json_repair import repair_json
bad_json_str = """
{
"name": "山田太郎",
"age": 30
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"],
}
"""
good_json_string = repair_json(bad_json_str)
validate_json(good_json_string)
その3: クォート閉じ忘れ
from json_repair import repair_json
bad_json_str = """
{
"name": "山田太郎,
"age": 30,
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"],
}
"""
good_json_string = repair_json(bad_json_str)
validate_json(good_json_string)
Validation Success!
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": [
"読書",
"ゲーム"
]
}
その4: カッコ閉じ忘れ
from json_repair import repair_json
bad_json_str = """
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": ["読書", "ゲーム"
}
"""
good_json_string = repair_json(bad_json_str)
validate_json(good_json_string)
Validation Success!
{
"name": "山田太郎",
"age": 30,
"email": "yamada@example.com",
"hobbies": [
"読書",
"ゲーム"
]
}
json_repairはjson.loads()
を置き換えるjson_repair.loads()
を提供しているので、最初のコードも以下のように書き換えることができる。
from pydantic import BaseModel, ValidationError
from typing import List, Optional
import json
import json_repair
# Pydanticモデルの定義
class UserModel(BaseModel):
name: str
age: int
email: Optional[str] = None
hobbies: List[str]
# json_repairを使ったJSONバリデーション関数
def validate_json(json_str: str):
try:
safe_json_data = json_repair.loads(json_str)
item = UserModel(**safe_json_data)
print("Validation Success!\n")
print(json.dumps(item.dict(), indent=2, ensure_ascii=False))
except Exception as e:
if isinstance(e, ValidationError):
print("Validation Error!\n")
print(json.dumps(json.loads(e.json()), indent=2, ensure_ascii=False))
else:
print(f"Unexpected error: {str(e)}")
json_repairが少なくともJSONとしての文法は正しくしてくれるので、json.JSONDecodeError
は不要になるということか。
なお、同じことをrepair_json
でやるならこうなる。
(snip)
try:
safe_json_data = json_repair.repair_json(json_str, return_objects=True)
item = UserModel(**safe_json_data)
(snip)
JSON「ファイル」の修正
不正なJSONファイルを作成
%%writefile bad_json_sample.json
{
# コメント入り
"name": "山田太郎",
# カンマ抜け
"age": 30
# カッコ閉じ忘れ
"hobbies": ["読書",
# ケツカンマ
"email": "yamada@example.com",
}
ファイルオブジェクトで渡す、サンプルのようにrb
で開いてjson_repair.load
に渡すと空文字列になってしまったので、少し書き換えている。
import json_repair
with open("bad_json_sample.json", 'r') as f:
cleaned_json_from_file = json_repair.load(f)
print(json.dumps(cleaned_json_from_file, indent=2, ensure_ascii=False))
ファイルをjson_repair.from_file()
で開く
import json_repair
import json
cleaned_json_from_file = json_repair.from_file("bad_json_sample.json")
print(json.dumps(cleaned_json_from_file, indent=2, ensure_ascii=False))
どちらも以下となる。
{
"name": "山田太郎",
"age": 30,
"hobbies": [
"読書",
"ケツカンマ",
"email",
"yamada@example.com"
]
}
json_repairは、高速な外部のJSONライブラリに依存しない設計になっている。場合によってはパフォーマンスが出ない可能性もある。その場合は以下を参考に。
CLIもある。
!json_repair -h
usage: json_repair [-h] [-i] [-o TARGET] [--ensure_ascii] [--indent INDENT] filename
Repair and parse JSON files.
positional arguments:
filename The JSON file to repair
options:
-h, --help show this help message and exit
-i, --inline Replace the file inline instead of returning the output to stdout
-o TARGET, --output TARGET
If specified, the output will be written to TARGET filename instead of
stdout
--ensure_ascii Pass ensure_ascii=True to json.dumps()
--indent INDENT Number of spaces for indentation (Default 2)
例えば先ほどのファイルに出力した不正なJSONファイルを食わせてみる。
!json_repair bad_json_sample.json
{
"name": "山田太郎",
"age": 30,
"hobbies": [
"読書",
"ケツカンマ",
"email",
"yamada@example.com"
]
}
便利。