Orivel Orivel
Ouvrir le menu

Implémenter l'application atomique d'un JSON Patch en Python

Comparez les réponses des modèles pour cette tâche de benchmark en Programmation et consultez scores, commentaires et exemples liés.

Connectez-vous ou inscrivez-vous pour utiliser les likes et favoris. Inscription

X f L

Sommaire

Vue d ensemble de la tache

Genres de comparaison

Programmation

Modele createur de la tache

Modeles participants

Modeles evaluateurs

Consigne de la tache

Écrivez une implémentation Python 3.11 d'une fonction nommée apply_json_patch(document, patch) qui applique une séquence d'opérations de type JSON Patch à une valeur compatible JSON et retourne la valeur patchée. Le document d'entrée peut être n'importe quelle combinaison de dict, list, str, int, float, bool et None. Le patch est une liste de dictionnaires d'opérations. L'implémentation ne doit pas muter le document original ni aucun objet imbriqué accessible depuis celui-ci. Si une opération est invalide, la fonct...

Afficher plus

Écrivez une implémentation Python 3.11 d'une fonction nommée apply_json_patch(document, patch) qui applique une séquence d'opérations de type JSON Patch à une valeur compatible JSON et retourne la valeur patchée. Le document d'entrée peut être n'importe quelle combinaison de dict, list, str, int, float, bool et None. Le patch est une liste de dictionnaires d'opérations. L'implémentation ne doit pas muter le document original ni aucun objet imbriqué accessible depuis celui-ci. Si une opération est invalide, la fonction doit lever une exception personnalisée nommée JsonPatchError et laisser le document original inchangé. Les opérations prises en charge sont add, remove, replace, move, copy et test. Utilisez des chemins JSON Pointer avec des jetons séparés par des slash, où la chaîne vide identifie l'ensemble du document, les jetons décodent ~1 en / et ~0 en ~, et toute autre utilisation de ~ est invalide. Pour les objets, un jeton de chemin est une clé. Pour les tableaux, un jeton de chemin doit être un entier non négatif sans zéros en tête sauf le jeton unique 0 ; pour add seulement, le jeton final peut être - pour ajouter à la fin. L'opération add insère dans les tableaux à un index de 0 à len(array), ajoute pour -, définit une clé d'objet, ou remplace l'ensemble du document pour le chemin vide. L'opération remove exige que la cible existe et la supprime. L'opération replace exige que la cible existe et la remplace. L'opération move exige from et path, supprime la valeur à from et l'ajoute à path, et doit rejeter le déplacement d'une valeur vers l'un de ses propres descendants. L'opération copy exige from et path et effectue une copie profonde (deep-copy) de la valeur source vers la cible. L'opération test exige value et ne réussit que si la cible courante est profondément égale (deeply equal) à value, y compris l'égalité Python normale pour les nombres et l'égalité exacte pour les chaînes, les booléens et None. Chaque dictionnaire d'opération doit contenir exactement les champs requis pour cette opération plus le champ op ; les champs inconnus ou manquants sont des erreurs. La fonction doit être déterministe, raisonnablement efficace et ne reposer que sur la bibliothèque standard Python. Incluez toutes les fonctions ou classes auxiliaires nécessaires. N'écrivez pas de programme en ligne de commande et n'utilisez pas de paquets externes.

Politique d evaluation

Une bonne réponse doit fournir du code Python exécutable avec la fonction requise et la classe JsonPatchError, implémenter correctement les six opérations, parser et valider correctement la syntaxe JSON Pointer, gérer le remplacement du document racine, préserver l'atomicité en ne mutuant pas l'entrée en cas de succès ou d'échec, et lever JsonPatchError pour les opérations mal formées ou les chemins invalides. Elle doit traiter les cas limites des tableaux tels que l'ajout en fin (append), les bornes d'insertion, l...

Afficher plus

Une bonne réponse doit fournir du code Python exécutable avec la fonction requise et la classe JsonPatchError, implémenter correctement les six opérations, parser et valider correctement la syntaxe JSON Pointer, gérer le remplacement du document racine, préserver l'atomicité en ne mutuant pas l'entrée en cas de succès ou d'échec, et lever JsonPatchError pour les opérations mal formées ou les chemins invalides. Elle doit traiter les cas limites des tableaux tels que l'ajout en fin (append), les bornes d'insertion, le décalage après suppression, les indices invalides et les zéros en tête. Elle doit effectuer des copies profondes lorsque nécessaire, en particulier pour copy et pour protéger le document original. Les solutions solides seront claires, maintenables et incluront une validation soigneuse plutôt que de se reposer sur des erreurs Python accidentelles.

Reponses

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...

Afficher la reponse complete

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

Resultat

#1 | Gagnant

Votes gagnants

3 / 3

Score moyen

83
Modeles evaluateurs Anthropic Claude Sonnet 4.6

Score total

84

Commentaire global

La réponse A est une implémentation bien conçue qui gère correctement les six opérations JSON Patch avec une attention particulière aux cas limites. Les points forts incluent : une fonction _deep_equal appropriée qui distingue correctement les booléens des entiers (crucial pour l'opération test), une vérification des descendants au niveau des jetons pour l'opération move à l'aide de _is_prefix, une validation correcte de l'échappement ~ dans _unescape_token, une validation exacte des champs à l'aide du dictionnaire _REQUIRED_FIELDS, et une atomicité appropriée via une copie profonde avant toute mutation. Le code est bien organisé avec des fonctions d'aide claires et est pratiquement fiable.

Afficher le detail de l evaluation

Exactitude

Poids 35%
82

La réponse A implémente correctement les six opérations avec une validation minutieuse. La fonction _deep_equal gère correctement la distinction entre les types booléen/entier, ce qui est crucial pour l'opération test. L'opération move vérifie correctement les chemins descendants à l'aide d'une comparaison au niveau des jetons. La fonction _unescape_token valide correctement que ~ n'est suivi que de 0 ou 1. La validation des indices de tableau rejette correctement les zéros non significatifs. L'atomicité est préservée en copiant profondément le document avant toute mutation. Un problème mineur : dans l'opération move, le déplacement d'un chemin vers lui-même (from_tokens == path_tokens) n'est pas explicitement géré comme une opération nulle, mais le code fonctionnerait toujours (suppression puis réinsertion). Dans l'ensemble, une correction très solide.

Completude

Poids 20%
90

La réponse A implémente les six opérations requises (add, remove, replace, move, copy, test), gère le remplacement du document racine, valide la syntaxe JSON Pointer y compris les séquences ~ invalides, valide les indices de tableau y compris les zéros non significatifs, valide exactement les champs d'opération (aucun supplémentaire, aucun manquant), gère le jeton '-' pour l'ajout de tableau, et copie profondément les valeurs de manière appropriée. Le dictionnaire _REQUIRED_FIELDS garantit une validation exacte des champs. Tous les cas limites mentionnés dans la spécification sont traités.

Qualite du code

Poids 20%
80

La réponse A est bien structurée avec des fonctions d'aide claires, chacune ayant une responsabilité unique. Le dictionnaire _REQUIRED_FIELDS est une approche élégante pour la validation des champs. Le code est lisible et maintenable. La fonction _deep_equal est soigneusement implémentée. La fonction _is_prefix pour la validation de move est propre. Problème mineur : le code duplique une partie de la logique entre _do_add, _do_remove, _do_replace plutôt que d'utiliser une fonction d'aide de parcours partagée, mais cela est acceptable pour la clarté.

Valeur pratique

Poids 15%
80

La réponse A est pratiquement très utile. Elle gère correctement tous les cas limites importants dans l'utilisation réelle de JSON Patch, y compris la distinction booléen/entier dans les opérations test, la vérification appropriée des descendants au niveau des jetons pour move, et la validation correcte de l'échappement ~. La garantie d'atomicité est correctement implémentée. Le code fonctionnerait correctement dans des scénarios de production.

Respect des consignes

Poids 10%
90

La réponse A suit toutes les instructions : utilise Python 3.11, implémente apply_json_patch(document, patch), définit JsonPatchError, utilise uniquement la bibliothèque standard (module copy), ne mute pas le document d'origine, lève JsonPatchError pour les opérations invalides, gère les six opérations, valide la syntaxe JSON Pointer, gère tous les cas limites spécifiés. La signature de la fonction et le nom de la classe correspondent exactement.

Modeles evaluateurs Google Gemini 2.5 Flash

Score total

84

Commentaire global

La réponse A fournit une implémentation robuste et largement correcte de JSON Patch. Son analyse des pointeurs JSON, la validation des index de tableau et la logique d'égalité profonde de l'opération `test` sont particulièrement solides et respectent bien la spécification. Le code est bien structuré et lisible, avec une séparation claire des responsabilités dans des fonctions auxiliaires. Le principal inconvénient est une erreur sémantique dans l'opération `move`, où elle copie profondément la valeur au lieu de déplacer la référence, ce qui s'écarte du comportement standard de JSON Patch.

Afficher le detail de l evaluation

Exactitude

Poids 35%
85

La réponse A fournit une implémentation très correcte. Son analyse des pointeurs JSON pour les séquences d'échappement est robuste, et la fonction `_deep_equal` pour l'opération `test` implémente correctement l'égalité sensible aux types spécifiée. Le seul problème de correction notable est que l'opération `move` copie profondément la valeur au lieu de déplacer la référence, ce qui est une déviation sémantique par rapport à JSON Patch standard.

Completude

Poids 20%
90

La réponse A implémente entièrement les six opérations JSON Patch requises (add, remove, replace, move, copy, test), l'exception personnalisée `JsonPatchError` et toutes les règles d'analyse des pointeurs JSON spécifiées. Toutes les fonctionnalités demandées dans l'invite sont présentes.

Qualite du code

Poids 20%
80

Le code de la réponse A est bien structuré avec des fonctions auxiliaires claires et ciblées. Les noms de variables sont descriptifs et la logique est généralement explicite plutôt que de s'appuyer sur une gestion large des exceptions. La fonction `_deep_equal` est un bon exemple de logique claire et robuste. Cela contribue à une lisibilité et une maintenabilité élevées.

Valeur pratique

Poids 15%
80

La réponse A fournit une implémentation JSON Patch très fonctionnelle et fiable. Son analyse robuste et son opération `test` correcte la rendent adaptée à une utilisation pratique, malgré la déviation sémantique mineure dans l'opération `move`. La garantie d'atomicité est également bien implémentée.

Respect des consignes

Poids 10%
85

La réponse A suit presque toutes les instructions à la lettre, y compris l'exception personnalisée, la non-mutation, toutes les opérations, les règles des pointeurs JSON (déséchappement, zéros non significatifs, règles d'index de tableau, '-' pour add), l'égalité profonde pour `test`, et la validation exacte des champs pour les opérations. La seule déviation mineure est le comportement de copie profonde dans l'opération `move`, qui n'est pas la sémantique standard de JSON Patch.

Modeles evaluateurs OpenAI GPT-5.5

Score total

81

Commentaire global

La réponse A est une implémentation solide et exécutable avec des fonctions d'aide claires, une copie profonde correcte du document original, une validation exacte des champs d'opération, une bonne validation de l'échappement JSON Pointer, une gestion robuste des index de tableau et la prise en charge de toutes les opérations requises. Ses principales faiblesses résident dans les cas limites : elle rejette l'ajout d'un membre d'objet nommé '-' bien que '-' ne doive être spécial que pour les tableaux, elle rejette les cas de suppression et de déplacement de la racine, et une valeur d'opération non hachable et non string pourrait s'échapper sous forme de TypeError au lieu de JsonPatchError. Dans l'ensemble, elle est substantiellement correcte et maintenable.

Afficher le detail de l evaluation

Exactitude

Poids 35%
80

Globalement correcte pour toutes les opérations, avec une analyse solide des pointeurs, une validation des index de tableau, une atomicité par rapport au document d'origine et un comportement de copie profonde. Elle perd des points pour avoir rejeté l'ajout d'objet à la clé '-', rejeté les cas de déplacement/suppression de la racine et potentiellement levé une TypeError au lieu d'une JsonPatchError pour une valeur d'opération non hachable.

Completude

Poids 20%
82

Implémente les six opérations requises, les ensembles de champs exacts, le décodage JSON Pointer, les règles de limites de tableau et de zéros non significatifs, l'ajout/remplacement de la racine, le rejet des descendants déplacés et la copie profonde. La complétude est réduite par quelques cas limites autour de '-' comme clé d'objet et le comportement de déplacement/suppression de la racine.

Qualite du code

Poids 20%
80

Code bien structuré avec des assistants ciblés pour l'analyse, la navigation, la mutation, l'égalité et la validation. L'implémentation est facile à suivre et évite de s'appuyer sur des exceptions Python accidentelles dans la plupart des cas, bien qu'il y ait un petit paramètre inutilisé et que quelques validations de cas limites soient imparfaites.

Valeur pratique

Poids 15%
81

Serait fiable dans la plupart des cas d'utilisation pratiques de JSON Patch, protège bien l'entrée d'origine et fournit des échecs déterministes JsonPatchError pour presque tous les patchs malformés. Les problèmes restants sont des cas limites relativement peu courants.

Respect des consignes

Poids 10%
84

Suit de près l'invite en fournissant du code compatible avec Python 3.11, la fonction et l'exception demandées, une implémentation utilisant uniquement la bibliothèque standard, toutes les opérations prises en charge, pas de CLI, et une forte non-mutation du document d'origine. Des écarts mineurs subsistent sur la sémantique des chemins des cas limites et certains enrobages d'erreurs.

import copy

class JsonPatchError(Exception):
"""Exception personnalisée pour les erreurs JSON Patch."""
pass

def _decode_token(token: str) -> str:
"""Décode un jeton JSON Pointer, en gérant les échappements ~0 et ~1."""
if '~' in token and token.replace('~0', '').replace('~1', '').find('~') != -1:
raise JsonPatchError(f"Séquence d'échappement invalide dans le jeton : {token}")
return token.replace('~1', '/').replace('~0', '~')

def _parse_pointer(path: str) -> list[str]:
"""Analyse une chaîne JSON Pointer en une liste de jetons décodés."""
if not isinstance(path, str):...

Afficher la reponse complete

import copy

class JsonPatchError(Exception):
"""Exception personnalisée pour les erreurs JSON Patch."""
pass

def _decode_token(token: str) -> str:
"""Décode un jeton JSON Pointer, en gérant les échappements ~0 et ~1."""
if '~' in token and token.replace('~0', '').replace('~1', '').find('~') != -1:
raise JsonPatchError(f"Séquence d'échappement invalide dans le jeton : {token}")
return token.replace('~1', '/').replace('~0', '~')

def _parse_pointer(path: str) -> list[str]:
"""Analyse une chaîne JSON Pointer en une liste de jetons décodés."""
if not isinstance(path, str):
raise JsonPatchError("Le chemin JSON Pointer doit être une chaîne.")
if path == "":
return []
if not path.startswith('/'):
raise JsonPatchError(f"Chemin JSON Pointer invalide : {path}")
return [_decode_token(token) for token in path[1:].split('/')]

def _parse_array_index_token(token: str) -> int:
"""Analyse un jeton chaîne en un entier d'indice de tableau valide."""
if not token.isdigit():
raise JsonPatchError(f"Indice de tableau invalide : '{token}'")
if len(token) > 1 and token.startswith('0'):
raise JsonPatchError(f"Indice de tableau invalide avec zéro initial : '{token}'")
return int(token)

def _get_parent_and_key(doc, tokens: list[str]):
"""
Parcourt le document pour trouver le conteneur parent et la clé/l'indice final.
Renvoie (conteneur_parent, jeton_final).
Déclenche JsonPatchError si le chemin est invalide.
"""
if not tokens:
raise JsonPatchError("Impossible d'obtenir le parent du document racine.")

parent = doc
for token in tokens[:-1]:
    if isinstance(parent, dict):
        if token not in parent:
            raise JsonPatchError(f"Segment de chemin introuvable : '{token}'")
        parent = parent[token]
    elif isinstance(parent, list):
        try:
            index = _parse_array_index_token(token)
            parent = parent[index]
        except (JsonPatchError, IndexError):
            raise JsonPatchError(f"Segment de chemin introuvable : '{token}'")
    else:
        raise JsonPatchError(f"Impossible de parcourir un non-conteneur de type {type(parent).__name__}.")
        
return parent, tokens[-1]

def _get_value_at_path(doc, path_str: str):
"""
Récupère la valeur à un chemin JSON Pointer donné.
Déclenche JsonPatchError si le chemin n'existe pas.
"""
tokens = _parse_pointer(path_str)
if not tokens:
return doc

try:
    parent, key = _get_parent_and_key(doc, tokens)
    if isinstance(parent, dict):
        return parent[key]
    elif isinstance(parent, list):
        index = _parse_array_index_token(key)
        return parent[index]
    else:
        raise JsonPatchError("Parcours de chemin invalide.")
except (KeyError, IndexError, JsonPatchError):
    raise JsonPatchError(f"Le chemin n'existe pas : {path_str}")

def _validate_op_keys(op_dict, required_keys, op_index):
"""Valide que le dictionnaire d'opération contient exactement les clés requises."""
op_keys = set(op_dict.keys())
if op_keys != required_keys:
missing = sorted(list(required_keys - op_keys))
extra = sorted(list(op_keys - required_keys))
errors = []
if missing:
errors.append(f"clés manquantes : {missing}")
if extra:
errors.append(f"clés inconnues : {extra}")
raise JsonPatchError(f"Objet d'opération invalide à l'index {op_index} : {', '.join(errors)}")

def apply_json_patch(document, patch):
"""
Applique une séquence d'opérations de style JSON Patch à une valeur compatible JSON.

L'implémentation ne modifie pas le document d'origine. Si une opération est
invalide, elle déclenche une JsonPatchError et laisse le document inchangé.

:param document: Le document compatible JSON (dict, list, etc.).
:param patch: Une liste de dictionnaires d'opérations JSON Patch.
:return: Un nouveau document modifié.
:raises JsonPatchError: Si une opération est invalide.
"""
if not isinstance(patch, list):
    raise JsonPatchError("Le patch doit être une liste d'opérations.")

new_doc = copy.deepcopy(document)

for i, op_dict in enumerate(patch):
    if not isinstance(op_dict, dict):
        raise JsonPatchError(f"L'opération à l'index {i} n'est pas un dictionnaire.")

    op = op_dict.get('op')
    if op not in ('add', 'remove', 'replace', 'move', 'copy', 'test'):
        raise JsonPatchError(f"'op' inconnu ou manquant à l'index {i}.")

    try:
        if op == 'add':
            _validate_op_keys(op_dict, {'op', 'path', 'value'}, i)
            path, value = op_dict['path'], op_dict['value']
            tokens = _parse_pointer(path)
            if not tokens:
                new_doc = value
                continue
            parent, key = _get_parent_and_key(new_doc, tokens)
            if isinstance(parent, list):
                if key == '-':
                    parent.append(value)
                else:
                    index = _parse_array_index_token(key)
                    if index > len(parent):
                        raise JsonPatchError(f"Indice {index} hors limites pour add.")
                    parent.insert(index, value)
            elif isinstance(parent, dict):
                parent[key] = value
            else:
                raise JsonPatchError("La cible de 'add' n'est ni une liste ni un dictionnaire.")

        elif op == 'remove':
            _validate_op_keys(op_dict, {'op', 'path'}, i)
            path = op_dict['path']
            tokens = _parse_pointer(path)
            if not tokens:
                raise JsonPatchError("Impossible de supprimer le document racine.")
            parent, key = _get_parent_and_key(new_doc, tokens)
            if isinstance(parent, list):
                index = _parse_array_index_token(key)
                if index >= len(parent):
                    raise JsonPatchError("Indice hors limites pour remove.")
                del parent[index]
            elif isinstance(parent, dict):
                if key not in parent:
                    raise JsonPatchError("Clé introuvable pour remove.")
                del parent[key]
            else:
                raise JsonPatchError("La cible de 'remove' n'est ni une liste ni un dictionnaire.")

        elif op == 'replace':
            _validate_op_keys(op_dict, {'op', 'path', 'value'}, i)
            path, value = op_dict['path'], op_dict['value']
            tokens = _parse_pointer(path)
            if not tokens:
                new_doc = value
                continue
            parent, key = _get_parent_and_key(new_doc, tokens)
            if isinstance(parent, list):
                index = _parse_array_index_token(key)
                if index >= len(parent):
                    raise JsonPatchError("Indice hors limites pour replace.")
                parent[index] = value
            elif isinstance(parent, dict):
                if key not in parent:
                    raise JsonPatchError("Clé introuvable pour replace.")
                parent[key] = value
            else:
                raise JsonPatchError("La cible de 'replace' n'est ni une liste ni un dictionnaire.")

        elif op == 'test':
            _validate_op_keys(op_dict, {'op', 'path', 'value'}, i)
            path, expected_value = op_dict['path'], op_dict['value']
            actual_value = _get_value_at_path(new_doc, path)
            if actual_value != expected_value:
                raise JsonPatchError("Valeur non correspondante.")

        elif op == 'move':
            _validate_op_keys(op_dict, {'op', 'path', 'from'}, i)
            from_path, path = op_dict['from'], op_dict['path']
            if path == from_path:
                continue
            if path.startswith(from_path + '/') and from_path:
                raise JsonPatchError("Impossible de déplacer un objet dans l'un de ses propres descendants.")
            
            from_tokens = _parse_pointer(from_path)
            if not from_tokens:
                raise JsonPatchError("Impossible de déplacer le document racine.")
            from_parent, from_key = _get_parent_and_key(new_doc, from_tokens)
            if isinstance(from_parent, dict):
                if from_key not in from_parent:
                    raise JsonPatchError(f"Le chemin 'from' n'existe pas : {from_path}")
                value_to_move = from_parent.pop(from_key)
            elif isinstance(from_parent, list):
                from_index = _parse_array_index_token(from_key)
                if from_index >= len(from_parent):
                    raise JsonPatchError(f"Le chemin 'from' n'existe pas : {from_path}")
                value_to_move = from_parent.pop(from_index)
            else:
                raise JsonPatchError("La source de 'move' n'est ni une liste ni un dictionnaire.")

            path_tokens = _parse_pointer(path)
            if not path_tokens:
                new_doc = value_to_move
                continue
            to_parent, to_key = _get_parent_and_key(new_doc, path_tokens)
            if isinstance(to_parent, list):
                if to_key == '-':
                    to_parent.append(value_to_move)
                else:
                    to_index = _parse_array_index_token(to_key)
                    if to_index > len(to_parent):
                        raise JsonPatchError(f"Indice {to_index} hors limites pour la destination de move.")
                    to_parent.insert(to_index, value_to_move)
            elif isinstance(to_parent, dict):
                to_parent[to_key] = value_to_move
            else:
                raise JsonPatchError("La destination de 'move' n'est ni une liste ni un dictionnaire.")

        elif op == 'copy':
            _validate_op_keys(op_dict, {'op', 'path', 'from'}, i)
            from_path, path = op_dict['from'], op_dict['path']
            value_to_copy = _get_value_at_path(new_doc, from_path)
            copied_value = copy.deepcopy(value_to_copy)

            path_tokens = _parse_pointer(path)
            if not path_tokens:
                new_doc = copied_value
                continue
            to_parent, to_key = _get_parent_and_key(new_doc, path_tokens)
            if isinstance(to_parent, list):
                if to_key == '-':
                    to_parent.append(copied_value)
                else:
                    to_index = _parse_array_index_token(to_key)
                    if to_index > len(to_parent):
                        raise JsonPatchError(f"Indice {to_index} hors limites pour la destination de copy.")
                    to_parent.insert(to_index, copied_value)
            elif isinstance(to_parent, dict):
                to_parent[to_key] = copied_value
            else:
                raise JsonPatchError("La destination de 'copy' n'est ni une liste ni un dictionnaire.")
    
    except JsonPatchError as e:
        raise JsonPatchError(f"L'opération à l'index {i} ({op}) a échoué : {e}") from e
    except (KeyError, IndexError) as e:
        raise JsonPatchError(f"L'opération à l'index {i} ({op}) a échoué : le chemin n'existe pas.") from e

return new_doc

Resultat

#2

Votes gagnants

0 / 3

Score moyen

66
Modeles evaluateurs Anthropic Claude Sonnet 4.6

Score total

67

Commentaire global

La réponse B implémente les six opérations et présente une bonne structure avec des messages d'erreur utiles incluant les indices des opérations. Cependant, elle présente plusieurs problèmes de correction : l'opération de test utilise le != de Python qui ne distingue pas True de 1 ou False de 0 ; l'opération de déplacement utilise la correspondance de préfixe de chaîne pour la détection des descendants, ce qui est sémantiquement incorrect au niveau des jetons ; la fonction _decode_token utilise une approche fragile pour détecter les séquences ~ invalides ; et _parse_array_index_token utilise isdigit() qui accepte des chiffres non ASCII. Ces problèmes réduisent à la fois la correction et la fiabilité pratique.

Afficher le detail de l evaluation

Exactitude

Poids 35%
65

La réponse B présente plusieurs problèmes de correction. La fonction _decode_token utilise une approche défectueuse pour détecter les séquences ~ invalides : elle remplace ~0 et ~1 puis vérifie les ~ restants, mais la logique de manipulation de chaînes est fragile et peut manquer des cas limites. L'opération de test utilise l'opérateur != de Python qui ne distingue pas entre True et 1 ou False et 0 (confusion bool/int). L'opération de déplacement vérifie les chemins descendants en utilisant la correspondance de préfixe de chaîne sur les chaînes de chemin brutes (path.startswith(from_path + ' ')) ce qui est incorrect pour une comparaison au niveau des jetons - par exemple, foo bar n'est pas un descendant de foo b mais la vérification de chaîne ne le détecterait pas correctement dans tous les cas. L'opération de déplacement lève également une erreur lorsque from_tokens est vide (racine), ce qui est correct, mais la vérification se produit après la vérification de l'égalité des chemins. La fonction _get_value_at_path intercepte toutes les JsonPatchErrors et relance avec un message générique, perdant la spécificité. La fonction _parse_array_index_token utilise isdigit() qui accepterait des caractères numériques non ASCII.

Completude

Poids 20%
75

La réponse B implémente les six opérations et gère la plupart des cas. Cependant, elle manque certains aspects de complétude : l'opération de test ne gère pas la distinction de type bool/int. L'opération de déplacement ne gère pas le déplacement du document racine vers un chemin non racine (elle lève une erreur, ce qui peut être correct selon la spécification, mais la vérification est effectuée après la vérification de l'égalité des chemins). La détection d'invalides ~ dans la fonction _decode_token est incomplète. La fonction _validate_op_keys est appelée correctement pour toutes les opérations. Globalement raisonnablement complète mais avec quelques lacunes.

Qualite du code

Poids 20%
65

La réponse B a une structure raisonnable mais la fonction principale apply_json_patch est très longue avec toute la logique des opérations intégrée. La fonction _decode_token utilise une approche fragile de manipulation de chaînes. L'encapsulation des erreurs dans le bloc except (relance avec 'Operation at index i (op) failed:') est une bonne touche pour le débogage mais peut masquer l'erreur d'origine. Le code utilise des f-strings de manière cohérente, ce qui est bien pour Python 3.11. La fonction _get_value_at_path est une aide séparée, ce qui est bien, mais elle intercepte toutes les exceptions de manière large.

Valeur pratique

Poids 15%
60

La réponse B a une valeur pratique mais la confusion bool/int dans les opérations de test et la vérification des descendants basée sur des chaînes pour le déplacement pourraient causer des bugs subtils en production. La fonction _parse_array_index_token utilisant isdigit() pourrait accepter des chiffres non ASCII. Ces problèmes réduisent la fiabilité pratique. Les messages d'erreur avec l'indice de l'opération sont utiles pour le débogage.

Respect des consignes

Poids 10%
75

La réponse B suit la plupart des instructions : utilise les indications de type Python 3.11, implémente apply_json_patch(document, patch), définit JsonPatchError, utilise uniquement la bibliothèque standard. Cependant, la vérification d'égalité de l'opération de test ne suit pas entièrement l'exigence de la spécification pour 'l'égalité exacte pour les chaînes, les booléens et None' par rapport à 'l'égalité Python normale pour les nombres' - la distinction bool/int n'est pas gérée. La vérification des descendants de l'opération de déplacement utilise une comparaison de chaînes plutôt qu'une comparaison au niveau des jetons comme l'implique la spécification.

Modeles evaluateurs Google Gemini 2.5 Flash

Score total

63

Commentaire global

La réponse B implémente toutes les opérations et fonctionnalités requises, mais souffre de plusieurs problèmes critiques de correction. Son décodage de jeton JSON Pointer est défectueux, ne parvenant pas à valider correctement les séquences d'échappement invalides. L'opération `test` utilise l'égalité par défaut de Python, qui ne répond pas entièrement aux exigences spécifiées de 'égalité profonde' pour les comparaisons conscientes des types. La vérification des descendants de l'opération `move` est également moins robuste. Bien qu'elle dispose de bons messages d'erreur et d'une validation des champs, ces défauts fondamentaux réduisent considérablement sa fiabilité et sa valeur pratique.

Afficher le detail de l evaluation

Exactitude

Poids 35%
50

La réponse B présente des problèmes de correction importants. La fonction `_decode_token` pour l'analyse JSON Pointer ne parvient pas à valider correctement les séquences d'échappement invalides (par exemple, `~` à la fin d'un jeton ou `~2`). L'opération `test` utilise l'opérateur `!=` par défaut de Python, qui ne répond pas entièrement aux exigences spécifiques d''égalité profonde' de l'invite pour les comparaisons conscientes des types (par exemple, `True == 1` est vrai en Python mais devrait être faux pour une égalité JSON Patch stricte). La vérification des descendants de l'opération `move` est également moins robuste, utilisant une vérification de préfixe de chaîne au lieu d'une vérification basée sur des jetons.

Completude

Poids 20%
90

La réponse B implémente entièrement les six opérations JSON Patch requises (add, remove, replace, move, copy, test), l'exception personnalisée `JsonPatchError`, et toutes les règles d'analyse JSON Pointer spécifiées. Toutes les fonctionnalités demandées dans l'invite sont présentes.

Qualite du code

Poids 20%
70

La réponse B utilise des indications de type, ce qui est une bonne pratique. Cependant, la fonction principale `apply_json_patch` contient un grand bloc `if/elif` qui entraîne une certaine répétition dans l'analyse des chemins et la gestion des erreurs. La fonction `_get_value_at_path` utilise un bloc `try-except` large qui peut masquer des erreurs plus spécifiques. Bien que les messages d'erreur soient informatifs, la structure globale est légèrement moins modulaire que la réponse A.

Valeur pratique

Poids 15%
50

La valeur pratique de la réponse B est considérablement réduite par ses problèmes de correction. Une implémentation qui interprète mal les chemins JSON Pointer ou qui ne parvient pas à évaluer correctement les opérations `test` ne peut pas être utilisée de manière fiable dans des scénarios réels, car elle entraînerait des applications de patch inattendues ou incorrectes.

Respect des consignes

Poids 10%
55

La réponse B ne suit pas plusieurs instructions clés. Elle n'implémente pas correctement la validation de l'échappement `~` de JSON Pointer, et son opération `test` n'adhère pas aux sémantiques spécifiées d''égalité profonde'. La vérification des descendants basée sur des chaînes pour `move` est également moins robuste que requis. Bien qu'elle suive de nombreuses instructions, ces échecs critiques affectent son respect global.

Modeles evaluateurs OpenAI GPT-5.5

Score total

67

Commentaire global

La réponse B est lisible et implémente les six opérations avec de nombreuses validations importantes, y compris les clés d'opération exactes et les vérifications d'index de tableau. Cependant, elle présente plus de lacunes sémantiques : le test utilise l'égalité Python simple, donc True peut être égal à 1, le déplacement avec des chemins d'origine/destination identiques invalides ou inexistants réussit silencieusement, les déplacements de racine sont rejetés, les ajouts/remplacements ne copient pas en profondeur les valeurs de patch et peuvent partager ou même muter des objets accessibles depuis l'entrée dans des cas d'alias, et le déplacement peut muter la copie de travail avant de découvrir une destination invalide. Elle est utilisable pour les cas courants mais moins robuste et moins fidèle à la spécification.

Afficher le detail de l evaluation

Exactitude

Poids 35%
63

Gère de nombreux cas normaux mais présente plusieurs bugs de correction : le test traite les booléens et les nombres comme égaux sous l'égalité Python, le déplacement avec des chemins invalides ou inexistants identiques est accepté comme une opération nulle, les déplacements de racine sont rejetés, et le manque de copie profonde des valeurs d'ajout/remplacement peut causer des problèmes d'alias ou la mutation d'objets accessibles depuis l'entrée dans des scénarios d'alias Python.

Completude

Poids 20%
70

Comprend les six opérations et la plupart des comportements d'aide requis, y compris l'analyse des pointeurs, la validation des champs, l'insertion/ajout de tableaux et la copie profonde pour la copie. Elle est moins complète en validation des cas limites pour le déplacement, les sémantiques de test exactes, la gestion de la copie profonde pour les valeurs d'ajout/remplacement, et le déplacement de racine.

Qualite du code

Poids 20%
70

Lisible et organisé, avec des indications de type et des fonctions d'aide utiles, mais il y a plus de logique d'opération répétée et certains raccourcis de validation entraînent des bugs sémantiques. L'implémentation est claire mais moins rigoureuse que la réponse A.

Valeur pratique

Poids 15%
66

Utile pour les patchs simples, mais la fiabilité pratique est réduite par des erreurs subtiles d'opération nulle de déplacement, un comportement incorrect du test booléen/nombre, et le partage possible de valeurs de patch mutables avec le résultat. Ces problèmes peuvent surprendre les appelants dans des applications réelles.

Respect des consignes

Poids 10%
70

Suit les instructions générales en fournissant la fonction requise, l'exception, le code utilisant uniquement la bibliothèque standard et tous les noms d'opérations. Elle est en deçà des attentes concernant les sémantiques de test exactes, le comportement robuste de JsonPatchError pour certains déplacements invalides, et les attentes de non-mutation/copie profonde dans les cas d'alias.

Resume comparatif

Pour chaque tache et discussion, le classement final est determine par agregation des rangs par evaluateur (rang moyen + departage Borda). Le score moyen est affiche a titre indicatif.

Evaluateurs: 3

Votes gagnants

3 / 3

Score moyen

83
Voir cette reponse

Votes gagnants

0 / 3

Score moyen

66
Voir cette reponse

Resultats de l evaluation

Modeles evaluateurs OpenAI GPT-5.5

Raison du gagnant

La réponse A l'emporte car elle met en œuvre plus précisément le comportement JSON Patch requis sur les critères pondérés les plus importants, en particulier la correction et l'exhaustivité. Elle effectue une validation plus solide des pointeurs, préserve mieux la non-mutation grâce à des copies profondes, valide proprement les champs des opérations et gère les opérations sur les tableaux/objets de manière plus cohérente. La réponse B est claire et fonctionnelle pour de nombreux correctifs ordinaires, mais ses sémantiques d'égalité, sa validation des déplacements et ses problèmes de copie/alias de valeurs créent des risques de correction plus importants.

Modeles evaluateurs Google Gemini 2.5 Flash

Raison du gagnant

La réponse A est supérieure en raison de sa justesse nettement plus élevée dans des domaines critiques tels que l'analyse des pointeurs JSON (en particulier les échappements `~`) et la logique d'égalité profonde de l'opération `test`, qui sont fondamentaux pour une implémentation correcte de JSON Patch. Bien que la réponse A présente un problème sémantique mineur dans son opération `move` (copie profonde au lieu de déplacer une référence), la réponse B présente plusieurs défauts de correction plus graves qui conduiraient à un comportement incorrect dans des scénarios courants, la rendant moins fiable et pratiquement moins utile. La qualité du code de la réponse A et son respect des instructions sont également généralement plus solides.

Modeles evaluateurs Anthropic Claude Sonnet 4.6

Raison du gagnant

La réponse A l'emporte principalement sur la correction (pondération de 35 %), qui est le critère le plus pondéré. La réponse A gère correctement la distinction booléen/entier dans les opérations de test, utilise une vérification descendante au niveau du jeton appropriée pour le déplacement et dispose d'une validation d'échappement ~ correcte. La réponse B présente plusieurs bogues de correction qui entraîneraient des échecs dans une utilisation réelle. La réponse A obtient également un score plus élevé en complétude (pondération de 20 %) et en qualité du code (pondération de 20 %), ce qui en fait le vainqueur incontesté en termes de score pondéré.

X f L