0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでJSONの重複キーを辞書化前に止める

0
Posted at

2回書いた limit が、読み込み後には1個になった。Pythonの json.loads() で {"limit": 1, "limit": 100} を読むと、残るのは 100 だ。

LLMの出力をツールの引数に渡すなら、重複キーは辞書化前に止めたい。消えたほうの値は、後から追えないんだよね。

自分は strict=True も試して、同じ結果で止まった。名前から期待した厳しさと、チェック範囲が違った。

Pythonのjson.loadsで値が消えるところ

CPython 3.14.8、標準ライブラリで実行した。入力は手書きの検証用JSONだ。

import json

raw = '{"limit": 1, "limit": 100}'
print('default:', json.loads(raw))
print('strict:', json.loads(raw, strict=True))
print('object_hook:', json.loads(raw, object_hook=lambda obj: obj))
default: {'limit': 100}
strict: {'limit': 100}
object_hook: {'limit': 100}

整数かどうかを調べても、100 は通る。元の重複は辞書から復元できない。

標準ライブラリのソースも読んだ。Python版の JSONObject() は、キーと値のペアをため、通常は dict(pairs) に変換する。同じキーの後の値が残り、続いて object_hook を呼ぶ。

strict が制御するのは、文字列内の制御文字を許すかどうか。同ファイルの JSONDecoder の説明でも確認した。重複キーの拒否は含まれない。名前が紛らわしい。

object_pairs_hookで辞書化の直前に止める

object_pairs_hook には辞書化前のペアが順番どおり渡る。重複を見つけたら例外にする。

def unique_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError(f'duplicate key: {key!r}')
        result[key] = value
    return result

cases = {
    'normal': '{"limit": 1}',
    'same_value': '{"limit": 1, "limit": 1}',
    'nested': '{"args": {"limit": 1, "limit": 100}}',
    'escaped': r'{"limit": 1, "\u006cimit": 100}',
    'separate': '[{"limit": 1}, {"limit": 100}]',
}
for name, text in cases.items():
    try:
        value = json.loads(text, object_pairs_hook=unique_object)
        print(name, 'OK', value)
    except ValueError as exc:
        print(name, 'NG', exc)

続けて実行した結果。

normal OK {'limit': 1}
same_value NG duplicate key: 'limit'
nested NG duplicate key: 'limit'
escaped NG duplicate key: 'limit'
separate OK [{'limit': 1}, {'limit': 100}]

同じ値の重複も拒否する。

入れ子の args にもフックが呼ばれる。自前の再帰処理は不要だ。エスケープは先に解釈されるので、limit と \u006cimit も同じキーとして検出できた。

配列の別要素にある limit は通る。result はオブジェクトごとに作り直す。

ツールへ渡す手前に置く

流れは「生のJSON → 重複キー検査 → 引数の型・値の検証 → ツール実行」にする。

この関数が担当するのは重複キーだけ。許可する引数名や数値の上限、NaN などの非標準の数値の拒否は別に扱う。

SDKから辞書を受け取る場合、生の文字列を読む箇所に差し込む。既存の object_hook と併用すると object_pairs_hook が優先されるので、元の変換処理の移植も確認したい。

例外に出すのはキー名まで。入れ子のパスは付けていない。

LLMの引数処理では、パース後の検証に目が行きやすい。5ケースで、辞書化の前に消える情報も試験に含められる。SDKに変換を任せる箇所が増えるほど、生のJSONを検査できる位置の確認が効いてくると思う。既存の引数テストに重複キーの入力を1件足せば、今の処理がどう扱うかを確かめられる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?