import copy
class JsonPatchError(Exception):
"""Raised when a JSON Patch operation is invalid or fails."""
pass
def _unescape_token(token):
# Validate and decode a single JSON Pointer reference token.
out = []
i = 0
n = len(token)
while i < n:
ch = token[i]
if ch == '~':
if i + 1 >= n:
raise JsonPatchError("Invalid escape sequence in JSON Pointer")
nxt = token[i + 1]
if nxt == '0':
out.append('~')
elif nxt == '1':
out.append('/')
else:
raise Jso...
全文を表示 ▼
import copy
class JsonPatchError(Exception):
"""Raised when a JSON Patch operation is invalid or fails."""
pass
def _unescape_token(token):
# Validate and decode a single JSON Pointer reference token.
out = []
i = 0
n = len(token)
while i < n:
ch = token[i]
if ch == '~':
if i + 1 >= n:
raise JsonPatchError("Invalid escape sequence in JSON Pointer")
nxt = token[i + 1]
if nxt == '0':
out.append('~')
elif nxt == '1':
out.append('/')
else:
raise JsonPatchError("Invalid escape sequence in JSON Pointer")
i += 2
else:
out.append(ch)
i += 1
return ''.join(out)
def _parse_pointer(path):
if not isinstance(path, str):
raise JsonPatchError("JSON Pointer must be a string")
if path == "":
return []
if not path.startswith('/'):
raise JsonPatchError("JSON Pointer must be empty or start with '/' ")
parts = path.split('/')[1:]
return [_unescape_token(p) for p in parts]
def _is_valid_array_index(token, allow_dash=False):
if allow_dash and token == '-':
return True
if token == '0':
return True
if not token:
return False
if not token.isdigit():
return False
if token[0] == '0':
return False
return True
def _resolve_parent(doc, tokens, allow_dash_last=False):
# Navigate to the container that holds the final token.
# Returns (parent, final_token).
if not tokens:
raise JsonPatchError("Path refers to whole document; no parent")
current = doc
for tok in tokens[:-1]:
current = _descend(current, tok, allow_dash=False)
return current, tokens[-1]
def _descend(container, token, allow_dash=False):
if isinstance(container, dict):
if token not in container:
raise JsonPatchError("Object key not found: %r" % (token,))
return container[token]
elif isinstance(container, list):
if not _is_valid_array_index(token, allow_dash=allow_dash):
raise JsonPatchError("Invalid array index: %r" % (token,))
if token == '-':
raise JsonPatchError("Cannot descend into '-' on array")
idx = int(token)
if idx < 0 or idx >= len(container):
raise JsonPatchError("Array index out of range: %d" % idx)
return container[idx]
else:
raise JsonPatchError("Cannot navigate into non-container value")
def _get_value(doc, tokens):
current = doc
for tok in tokens:
current = _descend(current, tok, allow_dash=False)
return current
def _do_add(doc, tokens, value):
if not tokens:
return value
parent, last = _resolve_parent(doc, tokens, allow_dash_last=True)
if isinstance(parent, dict):
if last == '-':
raise JsonPatchError("'-' is not valid for object add")
parent[last] = value
elif isinstance(parent, list):
if not _is_valid_array_index(last, allow_dash=True):
raise JsonPatchError("Invalid array index for add: %r" % (last,))
if last == '-':
parent.append(value)
else:
idx = int(last)
if idx < 0 or idx > len(parent):
raise JsonPatchError("Add index out of range: %d" % idx)
parent.insert(idx, value)
else:
raise JsonPatchError("Cannot add into non-container value")
return doc
def _do_remove(doc, tokens):
if not tokens:
raise JsonPatchError("Cannot remove whole document")
parent, last = _resolve_parent(doc, tokens)
if isinstance(parent, dict):
if last not in parent:
raise JsonPatchError("Cannot remove nonexistent key: %r" % (last,))
del parent[last]
elif isinstance(parent, list):
if not _is_valid_array_index(last, allow_dash=False):
raise JsonPatchError("Invalid array index for remove: %r" % (last,))
idx = int(last)
if idx < 0 or idx >= len(parent):
raise JsonPatchError("Remove index out of range: %d" % idx)
del parent[idx]
else:
raise JsonPatchError("Cannot remove from non-container value")
return doc
def _do_replace(doc, tokens, value):
if not tokens:
return value
parent, last = _resolve_parent(doc, tokens)
if isinstance(parent, dict):
if last not in parent:
raise JsonPatchError("Cannot replace nonexistent key: %r" % (last,))
parent[last] = value
elif isinstance(parent, list):
if not _is_valid_array_index(last, allow_dash=False):
raise JsonPatchError("Invalid array index for replace: %r" % (last,))
idx = int(last)
if idx < 0 or idx >= len(parent):
raise JsonPatchError("Replace index out of range: %d" % idx)
parent[idx] = value
else:
raise JsonPatchError("Cannot replace in non-container value")
return doc
def _deep_equal(a, b):
# Exact type-aware equality. Disallow bool/number cross matches.
if isinstance(a, bool) or isinstance(b, bool):
if type(a) is not type(b):
return False
return a == b
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a == b
if type(a) is not type(b):
return False
if a is None:
return b is None
if isinstance(a, str):
return a == b
if isinstance(a, list):
if len(a) != len(b):
return False
return all(_deep_equal(x, y) for x, y in zip(a, b))
if isinstance(a, dict):
if set(a.keys()) != set(b.keys()):
return False
return all(_deep_equal(a[k], b[k]) for k in a)
return a == b
def _is_prefix(prefix_tokens, tokens):
if len(prefix_tokens) > len(tokens):
return False
return tokens[:len(prefix_tokens)] == prefix_tokens
_REQUIRED_FIELDS = {
"add": {"op", "path", "value"},
"remove": {"op", "path"},
"replace": {"op", "path", "value"},
"move": {"op", "path", "from"},
"copy": {"op", "path", "from"},
"test": {"op", "path", "value"},
}
def _validate_op_fields(operation):
if not isinstance(operation, dict):
raise JsonPatchError("Operation must be a dict")
if "op" not in operation:
raise JsonPatchError("Operation missing 'op' field")
op = operation["op"]
if op not in _REQUIRED_FIELDS:
raise JsonPatchError("Unknown operation: %r" % (op,))
required = _REQUIRED_FIELDS[op]
actual = set(operation.keys())
if actual != required:
raise JsonPatchError("Operation %r has invalid fields" % (op,))
return op
def apply_json_patch(document, patch):
if not isinstance(patch, list):
raise JsonPatchError("Patch must be a list of operations")
# Work on a deep copy so the original is never mutated.
working = copy.deepcopy(document)
for operation in patch:
op = _validate_op_fields(operation)
if op == "add":
tokens = _parse_pointer(operation["path"])
working = _do_add(working, tokens, copy.deepcopy(operation["value"]))
elif op == "remove":
tokens = _parse_pointer(operation["path"])
working = _do_remove(working, tokens)
elif op == "replace":
tokens = _parse_pointer(operation["path"])
working = _do_replace(working, tokens, copy.deepcopy(operation["value"]))
elif op == "move":
from_tokens = _parse_pointer(operation["from"])
path_tokens = _parse_pointer(operation["path"])
if _is_prefix(from_tokens, path_tokens) and from_tokens != path_tokens:
raise JsonPatchError("Cannot move into one of its own descendants")
value = _get_value(working, from_tokens)
value = copy.deepcopy(value)
working = _do_remove(working, from_tokens)
working = _do_add(working, path_tokens, value)
elif op == "copy":
from_tokens = _parse_pointer(operation["from"])
path_tokens = _parse_pointer(operation["path"])
value = _get_value(working, from_tokens)
value = copy.deepcopy(value)
working = _do_add(working, path_tokens, value)
elif op == "test":
tokens = _parse_pointer(operation["path"])
current = _get_value(working, tokens)
if not _deep_equal(current, operation["value"]):
raise JsonPatchError("Test operation failed")
return working
判定
勝利票
3 / 3
平均スコア
総合点
総評
回答Aは、6つのJSON Patch操作すべてを注意深くエッジケースを処理しながら正しく実装した、よくできた実装です。主な強みは次のとおりです。ブール値と整数を正しく区別する適切な_deep_equal関数(テスト操作に不可欠)、_is_prefixを使用したmove操作のトークンレベルの子孫チェック、_unescape_tokenでの正確な~エスケープ検証、_REQUIRED_FIELDS辞書を使用した正確なフィールド検証、および変更前のディープコピーによる適切なアトミック性です。コードは明確なヘルパー関数で整理されており、実用的に信頼できます。
採点詳細を表示 ▼
正確さ
重み 35%回答Aは、注意深い検証とともに、6つの操作すべてを正しく実装しています。_deep_equal関数は、テスト操作に不可欠なbool/int型の区別を適切に処理します。move操作は、トークンレベルの比較を使用して子孫パスを正しくチェックします。_unescape_token関数は、~が0または1にのみ続くことを正しく検証します。配列インデックス検証は、先頭ゼロを正しく拒否します。アトミック性は、変更前にドキュメントをディープコピーすることによって維持されます。軽微な問題1つ:move操作では、パスをそれ自体に移動すること(from_tokens == path_tokens)は明示的にノープとして処理されませんが、コードは(削除してから再度追加する)それでも機能します。全体的に非常に堅実な正しさです。
完全性
重み 20%回答Aは、6つの必須操作(add、remove、replace、move、copy、test)すべてを実装し、ルートドキュメントの置換を処理し、無効な~シーケンスを含むJSON Pointer構文を検証し、先頭ゼロを含む配列インデックスを検証し、操作フィールドを正確に検証し(余分なし、欠落なし)、配列追加用の'-'トークンを処理し、値を適切にディープコピーします。_REQUIRED_FIELDS辞書は正確なフィールド検証を保証します。仕様で言及されているすべてエッジケースに対処しています。
コード品質
重み 20%回答Aは、それぞれが単一の責任を持つ明確なヘルパー関数でよく構造化されています。_REQUIRED_FIELDS辞書はフィールド検証のためのエレガントなアプローチです。コードは読みやすく、保守しやすいです。_deep_equal関数は注意深く実装されています。move検証のための_is_prefix関数はクリーンです。軽微な問題:コードは、共有トラバーサルヘルパーを使用するのではなく、_do_add、_do_remove、_do_replace間で一部のロジックを重複させていますが、これは明確さのために許容範囲内です。
実用性
重み 15%回答Aは実用的に非常に役立ちます。テスト操作でのbool/intの区別、moveのための適切なトークンレベルの子孫チェック、および正確な~エスケープ検証を含む、実際のJSON Patchの使用で重要なすべてエッジケースを正しく処理します。アトミック性の保証は適切に実装されています。コードは本番環境シナリオで正しく機能するでしょう。
指示遵守
重み 10%回答Aは、すべての指示に従っています:Python 3.11を使用し、apply_json_patch(document, patch)を実装し、JsonPatchErrorを定義し、標準ライブラリ(copyモジュール)のみを使用し、元のドキュメントを変更せず、無効な操作に対してJsonPatchErrorを発生させ、6つの操作すべてを処理し、JSON Pointer構文を検証し、指定されたすべてエッジケースを処理します。関数シグネチャとクラス名は完全に一致しています。
総合点
総評
回答Aは、JSON Patchの堅牢で大部分が正しい実装を提供しています。特に、JSON Pointerの解析、配列インデックスの検証、`test`操作の深い等価性ロジックは強力で、仕様によく準拠しています。コードはよく構造化されており、ヘルパー関数への関心の分離が明確で読みやすいです。主な欠点は、`move`操作におけるセマンティックエラーで、参照を移動する代わりに値をディープコピーしてしまう点で、標準的なJSON Patchの動作から逸脱しています。
採点詳細を表示 ▼
正確さ
重み 35%回答Aは、非常に正しい実装を提供しています。JSON Pointerのバックスラッシュエスケープシーケンスの解析は堅牢であり、`test`操作のための`_deep_equal`関数は、指定された型を意識した等価性を正しく実装しています。唯一注目すべき正しさの問題は、`move`操作が参照を移動するのではなく値をディープコピーしてしまうことで、これは標準的なJSON Patchからのセマンティックな逸脱です。
完全性
重み 20%回答Aは、要求された6つのJSON Patch操作(add、remove、replace、move、copy、test)、カスタム`JsonPatchError`例外、および指定されたすべてのJSON Pointer解析ルールを完全に実装しています。プロンプトで要求されたすべての機能が存在します。
コード品質
重み 20%回答Aのコードは、明確で焦点を絞ったヘルパー関数により、よく構造化されています。変数名は説明的であり、ロジックは広範な例外処理に依存するのではなく、一般的に明示的です。`_deep_equal`関数は、明確で堅牢なロジックの良い例です。これは、高い可読性と保守性に貢献しています。
実用性
重み 15%回答Aは、非常に機能的で信頼性の高いJSON Patch実装を提供します。その堅牢な解析と正しい`test`操作は、`move`操作における軽微なセマンティックな逸脱にもかかわらず、実用的な使用に適しています。アトミック性の保証もよく実装されています。
指示遵守
重み 10%回答Aは、カスタム例外、非ミューテーション、すべての操作、JSON Pointerルール(エスケープ解除、先頭のゼロ、配列インデックスルール、追加のための'-')、`test`のための深い等価性、および操作のための正確なフィールド検証を含む、ほとんどすべての指示に正確に従っています。唯一の軽微な逸脱は、`move`操作におけるディープコピーの動作であり、これは標準的なJSON Patchのセマンティクスではありません。
総合点
総評
回答Aは、明確なヘルパー関数、元のドキュメントの適切なディープコピー、正確な操作フィールド検証、良好なJSON Pointerエスケープ検証、堅牢な配列インデックス処理、およびすべての必須操作のサポートを備えた、強力で実行可能な実装です。主な弱点はエッジケースです。'-'という名前のオブジェクトメンバーを追加することを拒否しますが、'-'は配列に対してのみ特別であるべきです。ルートの削除とルートの移動のケースを拒否します。また、非文字列のハッシュ不可能なop値がJsonPatchErrorではなくTypeErrorとしてエスケープする可能性があります。全体として、実質的に正しく保守可能です。
採点詳細を表示 ▼
正確さ
重み 35%すべての操作でほぼ正確であり、強力なポインタ解析、配列インデックス検証、元のドキュメントに対する原子性、およびディープコピーの動作を備えています。キー'-'へのオブジェクト追加、ルートの移動/削除ケースの拒否、およびハッシュ不可能なop値に対してJsonPatchErrorではなくTypeErrorが発生する可能性があることで減点されています。
完全性
重み 20%6つの必須操作すべて、正確なフィールドセット、JSON Pointerデコード、配列境界と先頭ゼロのルール、ルートの追加/置換、移動された子孫の拒否、およびコピーのディープコピーを実装しています。'-'をオブジェクトキーとして使用する場合のエッジケースと、ルートの移動/削除の動作に関するいくつかの問題により、完全性が低下しています。
コード品質
重み 20%解析、ナビゲーション、ミューテーション、等価性、検証のための焦点を絞ったヘルパーを備えた、よく構造化されたコードです。実装は理解しやすく、ほとんどの場所で偶発的なPython例外に依存しないようにしていますが、小さな未使用のパラメータがあり、いくつかのエッジ検証が不完全です。
実用性
重み 15%ほとんどの実用的なJSON Patchユースケースで信頼性が高く、元の入力を適切に保護し、ほとんどすべての不正なパッチに対して決定論的なJsonPatchErrorの失敗を提供します。残りの問題は比較的まれなエッジケースです。
指示遵守
重み 10%Python 3.11互換コード、要求された関数と例外、標準ライブラリのみの実装、サポートされているすべての操作、CLIなし、および元のドキュメントの強力な非変更性を提供することにより、プロンプトに厳密に従っています。エッジケースパスのセマンティクスと一部のエラーラッパーには、わずかな逸脱が残っています。