A Dart implementation of JSON Pointer (RFC 6901), JSON Patch (RFC 6902),
and JSON Merge Patch (RFC 7396). Its behavior and deterministic diff output match Rust
json-patch 4.2.0.
- All JSON Patch operations:
add,remove,replace,move,copy, andtest. - Transactional patching with rollback, plus an explicit unsafe partial-application API.
- JSON Merge Patch and deterministic positional diff generation.
- Immutable typed operations with
dart_mappableJSON serialization. - Structured errors carrying the failed operation, path, reason, and document state.
- Strict JSON and JSON Pointer validation.
JsonPatch.applyPatch mutates JSON maps and lists in place where possible and returns the resulting
root. Always retain the return value because a root-level operation can replace the document
entirely.
import 'dart:convert';
import 'package:json_patch/json_patch.dart';
Object? document = jsonDecode('[{"name":"Andrew"},{"name":"Maxim"}]');
final patch = Patch.fromJson([
{'op': 'test', 'path': '/0/name', 'value': 'Andrew'},
{'op': 'add', 'path': '/0/happy', 'value': true},
]);
document = JsonPatch.applyPatch(document, patch);Patch text can be decoded and encoded directly:
final patch = Patch.fromJsonString(
'[{"op":"replace","path":"/title","value":"Hello"}]',
);
final encoded = patch.toJsonString();Operations can also be constructed with typed paths:
final patch = Patch([
AddOperation(path: JsonPointer.parse('/tags/-'), value: 'dart'),
RemoveOperation(path: JsonPointer.parse('/obsolete')),
]);JsonPatch.applyPatch is transactional. If an operation fails, prior changes are rolled back and a
PatchException is thrown. JsonPatch.applyPatchUnsafe skips rollback; its exception's document
field contains the partially modified root, including after root replacement.
try {
document = JsonPatch.applyPatch(document, patch);
} on PatchException catch (error) {
document = error.document;
print('Operation ${error.operation} failed at ${error.path}: ${error.kind}');
}document = {'title': 'Goodbye', 'obsolete': true};
document = JsonPatch.applyMergePatch(document, {
'title': 'Hello',
'obsolete': null,
});As required by RFC 7396, a null object member removes that member, while a non-object patch
replaces the whole document.
final generated = JsonPatch.diff(
{'title': 'Goodbye', 'tags': ['example', 'sample']},
{'title': 'Hello', 'tags': ['example']},
);
final updated = JsonPatch.applyPatch(
{'title': 'Goodbye', 'tags': ['example', 'sample']},
generated,
);The diff algorithm intentionally matches Rust json-patch: objects follow input map order, arrays
are compared positionally, and generated patches do not synthesize move or copy. Exact operation
ordering on Rust requires serde_json's preserve_order feature.
Documents and operation values must contain only null, booleans, finite numbers, strings, lists,
and string-keyed maps. Inserted values are deeply copied, so patch and document collections never
alias one another.
For Rust compatibility, integer 1 and floating-point 1.0 compare as different JSON values.
Exact numeric interoperability targets the Dart VM. The package remains web-compatible, but Dart
web cannot exactly represent integers outside JavaScript's 53-bit safe range.
Input documents must use mutable maps and lists if an operation modifies them. Values produced by
jsonDecode satisfy this requirement.
The test suite includes every enabled upstream JSON Patch, rollback, and JSON Merge Patch fixture, plus exact error-string checks. The implementation is also cross-checked against Rust for identical diff serialization and cross-runtime patch results.
This package is a Dart port of Ivan Dubrov's json-patch and is available under either
the MIT License or Apache License 2.0, at your option.