#!/usr/bin/env python3 # Standalone synthetic fixture; no user files or external network. import copy,json,hashlib def merge(target,patch): if not isinstance(patch,dict):return copy.deepcopy(patch) out=copy.deepcopy(target)if isinstance(target,dict)else{} for key,value in patch.items(): if value is None:out.pop(key,None) else:out[key]=merge(out.get(key),value) return out original={'title':'draft','description':'keep','tags':['review','source'],'profile':{'city':'Seoul','name':'editor'},'nullable':None} cases=[('omitted',{'title':'revised'},{'title':'revised','description':'keep','tags':['review','source'],'profile':{'city':'Seoul','name':'editor'},'nullable':None}),('delete-null',{'description':None},{'title':'draft','tags':['review','source'],'profile':{'city':'Seoul','name':'editor'},'nullable':None}),('replace-array',{'tags':['edited']},{'title':'draft','description':'keep','tags':['edited'],'profile':{'city':'Seoul','name':'editor'},'nullable':None}),('nested-delete',{'profile':{'city':None,'lang':'ko'}},{'title':'draft','description':'keep','tags':['review','source'],'profile':{'name':'editor','lang':'ko'},'nullable':None}),('replace-root',None,None),('delete-missing',{'missing':None},original)] before=copy.deepcopy(original);rows=[] for name,patch,expected in cases: actual=merge(original,patch);assert actual==expected and original==before assert merge(actual,patch)==actual rows.append({'case':name,'patch':patch,'expected':expected,'actual':actual,'repeatSamePatchUnchanged':True}) scalar=merge('old',{'priority':1});assert scalar=={'priority':1} result={'original':original,'cases':rows,'scalarTargetObjectPatch':scalar,'sourceUnmodified':original==before,'scope':'RFC7396-style pure value transformation. Null in object patch means delete; root null replaces whole document. No HTTP API called, no server authorization/concurrency guarantees inferred.'} import sys,json from datetime import datetime,timezone result={'experiment':'json-merge-patch-semantics','executedAt':datetime.now(timezone.utc).isoformat(),'runtime':sys.version.split()[0],'synthetic':True,**result} print(json.dumps(result,ensure_ascii=False,indent=2))