Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

Toolbox Dev — Ideo-Lab

JSONPatcher v0.2.0 — Deep Merge + Diff LCS + Apply Patch

Fusionner des structures imbriquĂ©es (dict/list), calculer un patch A→B, et rejouer ce patch. V2 : diff fin des listes via LCS → ins/del au lieu de “set liste entiĂšre”.

v0.2.0  ‱  Python 3.10+

À 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_none

Diff → Patch

Produire un patch stable et compréhensible.

setdelins

Apply Patch

Rejouer le patch (avec création auto des conteneurs si besoin).

create=Truepaths a.b[0].c
NouveautĂ© V2 : diff des listes via LCS (Longest Common Subsequence). Au lieu de remplacer toute une liste, JSONPatcher produit des opĂ©rations del / 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.

Télécharger le Wheel (jsonpatcher v0.2.0)

/static/toolbox/jsonpatcher-0.2.0-py3-none-any.whl

Commande

# venv activé
pip install /chemin/local/jsonpatcher-0.2.0-py3-none-any.whl

Sanity 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)"
RĂ©sultat →True + patch fin (ins/del)
Note : l’URL /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=...).

StrategyComportementCas d’usage
overrideoverride remplace baseOverrides env, config finale simple
keepgarde base si dĂ©jĂ  prĂ©sent“defaults” intouchables
appendconcatĂšne les listesajout de middlewares, plugins, routes
set_unionunion stable (sans doublons)liste de features, flags uniques
merge_by_idfusionne 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)
RĂ©sultat →{"a":{"b":[{"id":1,"c":1},{"id":2,"c":222},{"id":3,"c":3}]}}

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")))
RĂ©sultat →[1,2,2,3] | [1,2,3]

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

Patch ops :
  • {"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)
Patch typique → [{"op":"del","path":"items[1]"},{"op":"ins","path":"items[3]","value":5}]

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)
Patch (id-aware) → del items[0] + set items[0].v + ins items[1]
Note : le patch exact peut varier selon l’implĂ©mentation, mais l’idĂ©e est stable : conserver le plus possible, supprimer/insĂ©rer seulement ce qui change, et “set” sur champs modifiĂ©s.

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)
RĂ©sultat →True | {"items":[1,3,4,5]}

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)
RĂ©sultat →{"a":{"b":[{"c":123}]}}
Bonnes pratiques :
  • 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.

Dans le code, c’est 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

BesoinAPI
Fusion config base + overridedeep_merge(base, override, options=MergeOptions(...))
Patch A→Bpatch = diff(a, b)
Appliquer patchapply_patch(a, patch)
Suppression clé via override NoneMergeOptions(delete_none=True)
List objects by idmerge_by_id (merge) + diff id-aware (LCS)
IdĂ©e V3 possible : diff plus intelligent des listes (LCS + “move” ops), et support d’indices/slices plus avancĂ©s. Mais V2 couvre dĂ©jĂ  90% des besoins rĂ©els.