|
3 | 3 | """ |
4 | 4 |
|
5 | 5 | import operator |
6 | | -from collections.abc import MutableMapping, MutableSequence |
| 6 | +from collections.abc import Mapping, MutableMapping, MutableSequence |
7 | 7 | from functools import reduce |
8 | 8 | from typing import TYPE_CHECKING, Any, Literal |
9 | 9 |
|
@@ -51,7 +51,7 @@ def apply_json_patch[T: MutableMapping](op: JSONPatch, o: T) -> T: |
51 | 51 | numeric, e.g., {"1": "first", "2": "second"} |
52 | 52 | - Unsupported: JSON pointer values that refer to an entire object, e.g., |
53 | 53 | "" -- the JSON Patch must have a root element ("/") per the model. |
54 | | - - Unsupported: JSON pointer values taht refer to a nameless object, e.g., |
| 54 | + - Unsupported: JSON pointer values that refer to a nameless object, e.g., |
55 | 55 | "/" -- JSON allows object keys to be the empty string ("") but this is |
56 | 56 | disallowed by the application. |
57 | 57 | """ |
@@ -222,3 +222,31 @@ def apply_json_patch[T: MutableMapping](op: JSONPatch, o: T) -> T: |
222 | 222 | raise JSONPatchError(f"Unknown JSON Patch operation: {op.op}") |
223 | 223 |
|
224 | 224 | return o |
| 225 | + |
| 226 | + |
| 227 | +def apply_json_merge[T: MutableMapping](patch: Any, o: T) -> T: |
| 228 | + """Applies a patch to a mapping object as per the RFC7396 JSON Merge Patch. |
| 229 | +
|
| 230 | + Notably, this operation may only target a ``MutableMapping`` as an analogue |
| 231 | + of a JSON object. This means that any keyed value in a Mapping may be |
| 232 | + replaced, added, or removed by a JSON Merge. This is not appropriate for |
| 233 | + patches that need to perform more tactical updates, such as modifying |
| 234 | + elements of a ``Sequence``. |
| 235 | +
|
| 236 | + This function does not allow setting a field value in the target to `None`; |
| 237 | + instead, any `None` value in a patch is an instruction to remove that |
| 238 | + field from the target completely. |
| 239 | +
|
| 240 | + This function differs from the RFC in the following ways: it will not |
| 241 | + replace the entire target object with a new mapping (i.e., the target must |
| 242 | + be a Mapping). |
| 243 | + """ |
| 244 | + if isinstance(patch, Mapping): |
| 245 | + for k, v in patch.items(): |
| 246 | + if v is None: |
| 247 | + _ = o.pop(k, None) |
| 248 | + else: |
| 249 | + o[k] = apply_json_merge(v, o.get(k, {})) |
| 250 | + return o |
| 251 | + else: |
| 252 | + return patch |
0 commit comments