à quoi ça sert ?
JSONPatcher vise les cas oĂč tu manipules des structures âJSON-likeâ : dict et list imbriquĂ©s (configs YAML/JSON, payloads dâAPI, state applicatif). Le besoin rĂ©current est :
- Fusionner un base + un override selon une stratĂ©gie (override/keep/append/merge_by_idâŠ)
- Comparer A et B, produire un patch lisible
- Appliquer ce patch pour obtenir exactement B (ou migrer un state)
Deep Merge
Construire une config finale Ă partir dâun base + overrides.
overridekeepappend set_unionmerge_by_iddelete_noneDiff â Patch
Produire un patch stable et compréhensible.
setdelinsApply Patch
Rejouer le patch (avec création auto des conteneurs si besoin).
create=Truepaths a.b[0].cdel / ins (et set seulement lĂ oĂč câest nĂ©cessaire).Installation (.whl)
Le package est livrĂ© en wheel (installation rapide) et ne dĂ©pend dâaucune librairie externe. TĂ©lĂ©charge le wheel ci-dessous puis installe-le dans ton venv.
/static/toolbox/jsonpatcher-0.2.0-py3-none-any.whl
Commande
# venv activé
pip install /chemin/local/jsonpatcher-0.2.0-py3-none-any.whlSanity check
python -c "from jsonpatcher import diff, apply_patch; a={'x':[1,2,3]}; b={'x':[1,3,4]}; p=diff(a,b); apply_patch(a,p); print(a==b, p)"/static/... sert au tĂ©lĂ©chargement HTTP. Pour pip, utilise plutĂŽt le chemin local oĂč tu as copiĂ© le wheel.Deep Merge (stratĂ©gies)
deep_merge(base, override) fusionne récursivement dict/list. Pour les listes, la stratégie est configurable via MergeOptions(strategy=...).
| Strategy | Comportement | Cas dâusage |
|---|---|---|
override | override remplace base | Overrides env, config finale simple |
keep | garde base si dĂ©jĂ prĂ©sent | âdefaultsâ intouchables |
append | concatĂšne les listes | ajout de middlewares, plugins, routes |
set_union | union stable (sans doublons) | liste de features, flags uniques |
merge_by_id | fusionne liste de dict par clĂ© | liste dâobjets config (id/name) |
Exemple 1 â merge_by_id + delete_none
Fusionne des objets par id. Si override met None et delete_none=True, la clé est supprimée.
from jsonpatcher import deep_merge, MergeOptions
base = {"a":{"b":[{"id":1,"c":1},{"id":2,"c":2}]}, "x": 9}
override = {"a":{"b":[{"id":2,"c":222},{"id":3,"c":3}]}, "x": None}
out = deep_merge(base, override, options=MergeOptions(strategy="merge_by_id", delete_none=True))
print(out)Exemple 2 â append / set_union
from jsonpatcher import deep_merge, MergeOptions
print(deep_merge([1,2], [2,3], options=MergeOptions(strategy="append")))
print(deep_merge([1,2], [2,3], options=MergeOptions(strategy="set_union")))Diff LCS / Patch (V2)
diff(a,b) retourne un patch (liste dâops). En V2, les listes utilisent un diff LCS pour produire des changements fins : del (suppression) + ins (insertion) + set (modification ponctuelle).
{"op":"set","path":"a.b[0].c","value":123}{"op":"del","path":"a.x"}ou{"op":"del","path":"items[2]"}{"op":"ins","path":"items[3]","value":...}
Exemple A â liste simple (LCS)
from jsonpatcher import diff
a = {"items":[1,2,3,4]}
b = {"items":[1,3,4,5]}
patch = diff(a, b)
print(patch)Exemple B â liste dâobjets (match sur id)
Par dĂ©faut, si un item est un dict avec id, JSONPatcher utilise cette clĂ© comme identitĂ© pour LCS. RĂ©sultat : modifications âin placeâ + insertions/suppressions minimales.
from jsonpatcher import diff
a = {"items":[{"id":1,"v":10},{"id":2,"v":20}]}
b = {"items":[{"id":2,"v":222},{"id":3,"v":30}]}
patch = diff(a, b)
print(patch)Apply Patch
apply_patch(doc, patch) applique les opĂ©rations dans lâordre. Par dĂ©faut, create=True permet de crĂ©er les conteneurs manquants (dict/list) lors de set.
Roundtrip complet A â B
from jsonpatcher import diff, apply_patch
a = {"items":[1,2,3,4]}
b = {"items":[1,3,4,5]}
patch = diff(a, b)
apply_patch(a, patch)
print(a == b, a)Création automatique (create=True)
from jsonpatcher import apply_patch
doc = {}
patch = [{"op":"set","path":"a.b[0].c","value":123}]
apply_patch(doc, patch, create=True)
print(doc)- Si tu appliques des patches externes (non fiables), encapsule dans try/except (PatchApplyError).
- Pour éviter les effets de bord, applique sur une copie :
doc = copy.deepcopy(doc).
Avancé & Performance (LCS)
Pourquoi un garde-fou ?
Le LCS classique est en O(n*m) (matrice). Sur de grandes listes, ça peut exploser. JSONPatcher intÚgre un garde-fou : si len(a)*len(b) dépasse un seuil, il retombe sur un patch simple : set liste entiÚre.
DiffOptions.max_matrix_cells (dĂ©faut 50 000). Tu peux le baisser si tu veux un diff toujours âsafeâ, ou lâaugmenter si tes listes sont petites.IdentitĂ© des items de liste
Par dĂ©faut : si dict avec id â identitĂ© = id. Sinon identitĂ© âbest-effortâ. Ăa marche trĂšs bien pour des listes dâobjets config.
Cheat sheet
| Besoin | API |
|---|---|
| Fusion config base + override | deep_merge(base, override, options=MergeOptions(...)) |
| Patch AâB | patch = diff(a, b) |
| Appliquer patch | apply_patch(a, patch) |
| Suppression clé via override None | MergeOptions(delete_none=True) |
| List objects by id | merge_by_id (merge) + diff id-aware (LCS) |
