json_patch/
lib.rs

1//! A [JSON Patch (RFC 6902)](https://tools.ietf.org/html/rfc6902) and
2//! [JSON Merge Patch (RFC 7396)](https://tools.ietf.org/html/rfc7396) implementation for Rust.
3//!
4//! # Usage
5//!
6//! Add this to your *Cargo.toml*:
7//! ```toml
8//! [dependencies]
9//! json-patch = "*"
10//! ```
11//!
12//! # Examples
13//! Create and patch document using JSON Patch:
14//!
15//! ```rust
16//! #[macro_use]
17//! use json_patch::{Patch, patch};
18//! use serde_json::{from_value, json};
19//!
20//! # pub fn main() {
21//! let mut doc = json!([
22//!     { "name": "Andrew" },
23//!     { "name": "Maxim" }
24//! ]);
25//!
26//! let p: Patch = from_value(json!([
27//!   { "op": "test", "path": "/0/name", "value": "Andrew" },
28//!   { "op": "add", "path": "/0/happy", "value": true }
29//! ])).unwrap();
30//!
31//! patch(&mut doc, &p).unwrap();
32//! assert_eq!(doc, json!([
33//!   { "name": "Andrew", "happy": true },
34//!   { "name": "Maxim" }
35//! ]));
36//!
37//! # }
38//! ```
39//!
40//! Create and patch document using JSON Merge Patch:
41//!
42//! ```rust
43//! #[macro_use]
44//! use json_patch::merge;
45//! use serde_json::json;
46//!
47//! # pub fn main() {
48//! let mut doc = json!({
49//!   "title": "Goodbye!",
50//!   "author" : {
51//!     "givenName" : "John",
52//!     "familyName" : "Doe"
53//!   },
54//!   "tags":[ "example", "sample" ],
55//!   "content": "This will be unchanged"
56//! });
57//!
58//! let patch = json!({
59//!   "title": "Hello!",
60//!   "phoneNumber": "+01-123-456-7890",
61//!   "author": {
62//!     "familyName": null
63//!   },
64//!   "tags": [ "example" ]
65//! });
66//!
67//! merge(&mut doc, &patch);
68//! assert_eq!(doc, json!({
69//!   "title": "Hello!",
70//!   "author" : {
71//!     "givenName" : "John"
72//!   },
73//!   "tags": [ "example" ],
74//!   "content": "This will be unchanged",
75//!   "phoneNumber": "+01-123-456-7890"
76//! }));
77//! # }
78//! ```
79#![warn(missing_docs)]
80
81use jsonptr::{index::Index, Pointer, PointerBuf};
82use serde::{Deserialize, Serialize};
83use serde_json::{Map, Value};
84use std::fmt::{self, Display, Formatter};
85use thiserror::Error;
86
87// So users can instance `jsonptr::PointerBuf` and others without
88// having to explicitly match our `jsonptr` version.
89pub use jsonptr;
90
91#[cfg(feature = "diff")]
92mod diff;
93
94#[cfg(feature = "diff")]
95pub use self::diff::diff;
96
97struct WriteAdapter<'a>(&'a mut dyn fmt::Write);
98
99impl std::io::Write for WriteAdapter<'_> {
100    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
101        let s = std::str::from_utf8(buf).unwrap();
102        self.0
103            .write_str(s)
104            .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?;
105        Ok(buf.len())
106    }
107
108    fn flush(&mut self) -> Result<(), std::io::Error> {
109        Ok(())
110    }
111}
112
113macro_rules! impl_display {
114    ($name:ident) => {
115        impl Display for $name {
116            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117                let alternate = f.alternate();
118                if alternate {
119                    serde_json::to_writer_pretty(WriteAdapter(f), self)
120                        .map_err(|_| std::fmt::Error)?;
121                } else {
122                    serde_json::to_writer(WriteAdapter(f), self).map_err(|_| std::fmt::Error)?;
123                }
124                Ok(())
125            }
126        }
127    };
128}
129
130/// Representation of JSON Patch (list of patch operations)
131#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
132#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
133pub struct Patch(pub Vec<PatchOperation>);
134
135impl_display!(Patch);
136
137impl std::ops::Deref for Patch {
138    type Target = [PatchOperation];
139
140    fn deref(&self) -> &[PatchOperation] {
141        &self.0
142    }
143}
144
145/// JSON Patch 'add' operation representation
146#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
147#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
148pub struct AddOperation {
149    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
150    /// within the target document where the operation is performed.
151    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
152    pub path: PointerBuf,
153    /// Value to add to the target location.
154    pub value: Value,
155}
156
157impl_display!(AddOperation);
158
159/// JSON Patch 'remove' operation representation
160#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
161#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
162pub struct RemoveOperation {
163    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
164    /// within the target document where the operation is performed.
165    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
166    pub path: PointerBuf,
167}
168
169impl_display!(RemoveOperation);
170
171/// JSON Patch 'replace' operation representation
172#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
173#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
174pub struct ReplaceOperation {
175    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
176    /// within the target document where the operation is performed.
177    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
178    pub path: PointerBuf,
179    /// Value to replace with.
180    pub value: Value,
181}
182
183impl_display!(ReplaceOperation);
184
185/// JSON Patch 'move' operation representation
186#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
187#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
188pub struct MoveOperation {
189    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
190    /// to move value from.
191    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
192    pub from: PointerBuf,
193    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
194    /// within the target document where the operation is performed.
195    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
196    pub path: PointerBuf,
197}
198
199impl_display!(MoveOperation);
200
201/// JSON Patch 'copy' operation representation
202#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
203#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
204pub struct CopyOperation {
205    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
206    /// to copy value from.
207    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
208    pub from: PointerBuf,
209    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
210    /// within the target document where the operation is performed.
211    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
212    pub path: PointerBuf,
213}
214
215impl_display!(CopyOperation);
216
217/// JSON Patch 'test' operation representation
218#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
219#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
220pub struct TestOperation {
221    /// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
222    /// within the target document where the operation is performed.
223    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
224    pub path: PointerBuf,
225    /// Value to test against.
226    pub value: Value,
227}
228
229impl_display!(TestOperation);
230
231/// JSON Patch single patch operation
232#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
233#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
234#[serde(tag = "op")]
235#[serde(rename_all = "lowercase")]
236pub enum PatchOperation {
237    /// 'add' operation
238    Add(AddOperation),
239    /// 'remove' operation
240    Remove(RemoveOperation),
241    /// 'replace' operation
242    Replace(ReplaceOperation),
243    /// 'move' operation
244    Move(MoveOperation),
245    /// 'copy' operation
246    Copy(CopyOperation),
247    /// 'test' operation
248    Test(TestOperation),
249}
250
251impl_display!(PatchOperation);
252
253impl PatchOperation {
254    /// Returns a reference to the path the operation applies to.
255    pub fn path(&self) -> &Pointer {
256        match self {
257            Self::Add(op) => &op.path,
258            Self::Remove(op) => &op.path,
259            Self::Replace(op) => &op.path,
260            Self::Move(op) => &op.path,
261            Self::Copy(op) => &op.path,
262            Self::Test(op) => &op.path,
263        }
264    }
265}
266
267impl Default for PatchOperation {
268    fn default() -> Self {
269        PatchOperation::Test(TestOperation::default())
270    }
271}
272
273/// This type represents all possible errors that can occur when applying JSON patch
274#[derive(Debug, Error)]
275#[non_exhaustive]
276pub enum PatchErrorKind {
277    /// `test` operation failed because values did not match.
278    #[error("value did not match")]
279    TestFailed,
280    /// `from` JSON pointer in a `move` or a `copy` operation was incorrect.
281    #[error("\"from\" path is invalid")]
282    InvalidFromPointer,
283    /// `path` JSON pointer is incorrect.
284    #[error("path is invalid")]
285    InvalidPointer,
286    /// `move` operation failed because target is inside the `from` location.
287    #[error("cannot move the value inside itself")]
288    CannotMoveInsideItself,
289}
290
291impl From<jsonptr::index::ParseIndexError> for PatchErrorKind {
292    fn from(_: jsonptr::index::ParseIndexError) -> Self {
293        Self::InvalidPointer
294    }
295}
296
297impl From<jsonptr::index::OutOfBoundsError> for PatchErrorKind {
298    fn from(_: jsonptr::index::OutOfBoundsError) -> Self {
299        Self::InvalidPointer
300    }
301}
302
303/// This type represents all possible errors that can occur when applying JSON patch
304#[derive(Debug, Error)]
305#[error("operation '/{operation}' failed at path '{path}': {kind}")]
306#[non_exhaustive]
307pub struct PatchError {
308    /// Index of the operation that has failed.
309    pub operation: usize,
310    /// `path` of the operation.
311    pub path: PointerBuf,
312    /// Kind of the error.
313    pub kind: PatchErrorKind,
314}
315
316fn translate_error(kind: PatchErrorKind, operation: usize, path: &Pointer) -> PatchError {
317    PatchError {
318        operation,
319        path: path.to_owned(),
320        kind,
321    }
322}
323
324fn add(doc: &mut Value, path: &Pointer, value: Value) -> Result<Option<Value>, PatchErrorKind> {
325    let Some((parent, last)) = path.split_back() else {
326        // empty path, add is replace the value wholesale
327        return Ok(Some(std::mem::replace(doc, value)));
328    };
329
330    let mut parent = doc
331        .pointer_mut(parent.as_str())
332        .ok_or(PatchErrorKind::InvalidPointer)?;
333
334    match &mut parent {
335        Value::Object(obj) => Ok(obj.insert(last.decoded().into_owned(), value)),
336        Value::Array(arr) => {
337            let idx = last.to_index()?.for_len_incl(arr.len())?;
338            arr.insert(idx, value);
339            Ok(None)
340        }
341        _ => Err(PatchErrorKind::InvalidPointer),
342    }
343}
344
345fn remove(doc: &mut Value, path: &Pointer, allow_last: bool) -> Result<Value, PatchErrorKind> {
346    let Some((parent, last)) = path.split_back() else {
347        return Err(PatchErrorKind::InvalidPointer);
348    };
349    let mut parent = doc
350        .pointer_mut(parent.as_str())
351        .ok_or(PatchErrorKind::InvalidPointer)?;
352
353    match &mut parent {
354        Value::Object(obj) => match obj.remove(last.decoded().as_ref()) {
355            None => Err(PatchErrorKind::InvalidPointer),
356            Some(val) => Ok(val),
357        },
358        // XXX: is this really correct? semantically it seems off, `-` refers to an empty position, not the last element
359        Value::Array(arr) if allow_last && matches!(last.to_index(), Ok(Index::Next)) => {
360            Ok(arr.pop().unwrap())
361        }
362        Value::Array(arr) => {
363            let idx = last.to_index()?.for_len(arr.len())?;
364            Ok(arr.remove(idx))
365        }
366        _ => Err(PatchErrorKind::InvalidPointer),
367    }
368}
369
370fn replace(doc: &mut Value, path: &Pointer, value: Value) -> Result<Value, PatchErrorKind> {
371    let target = doc
372        .pointer_mut(path.as_str())
373        .ok_or(PatchErrorKind::InvalidPointer)?;
374    Ok(std::mem::replace(target, value))
375}
376
377fn mov(
378    doc: &mut Value,
379    from: &Pointer,
380    path: &Pointer,
381    allow_last: bool,
382) -> Result<Option<Value>, PatchErrorKind> {
383    // Check we are not moving inside own child
384    if path.starts_with(from) && path.len() != from.len() {
385        return Err(PatchErrorKind::CannotMoveInsideItself);
386    }
387    let val = remove(doc, from, allow_last).map_err(|err| match err {
388        PatchErrorKind::InvalidPointer => PatchErrorKind::InvalidFromPointer,
389        err => err,
390    })?;
391    add(doc, path, val)
392}
393
394fn copy(doc: &mut Value, from: &Pointer, path: &Pointer) -> Result<Option<Value>, PatchErrorKind> {
395    let source = doc
396        .pointer(from.as_str())
397        .ok_or(PatchErrorKind::InvalidFromPointer)?
398        .clone();
399    add(doc, path, source)
400}
401
402fn test(doc: &Value, path: &Pointer, expected: &Value) -> Result<(), PatchErrorKind> {
403    let target = doc
404        .pointer(path.as_str())
405        .ok_or(PatchErrorKind::InvalidPointer)?;
406    if *target == *expected {
407        Ok(())
408    } else {
409        Err(PatchErrorKind::TestFailed)
410    }
411}
412
413/// Patch provided JSON document (given as `serde_json::Value`) in-place. If any of the patch is
414/// failed, all previous operations are reverted. In case of internal error resulting in panic,
415/// document might be left in inconsistent state.
416///
417/// # Example
418/// Create and patch document:
419///
420/// ```rust
421/// #[macro_use]
422/// use json_patch::{Patch, patch};
423/// use serde_json::{from_value, json};
424///
425/// # pub fn main() {
426/// let mut doc = json!([
427///     { "name": "Andrew" },
428///     { "name": "Maxim" }
429/// ]);
430///
431/// let p: Patch = from_value(json!([
432///   { "op": "test", "path": "/0/name", "value": "Andrew" },
433///   { "op": "add", "path": "/0/happy", "value": true }
434/// ])).unwrap();
435///
436/// patch(&mut doc, &p).unwrap();
437/// assert_eq!(doc, json!([
438///   { "name": "Andrew", "happy": true },
439///   { "name": "Maxim" }
440/// ]));
441///
442/// # }
443/// ```
444pub fn patch(doc: &mut Value, patch: &[PatchOperation]) -> Result<(), PatchError> {
445    let mut undo_stack = Vec::with_capacity(patch.len());
446    if let Err(e) = apply_patches(doc, patch, Some(&mut undo_stack)) {
447        if let Err(e) = undo_patches(doc, &undo_stack) {
448            unreachable!("unable to undo applied patches: {e}")
449        }
450        return Err(e);
451    }
452    Ok(())
453}
454
455/// Patch provided JSON document (given as `serde_json::Value`) in-place. Different from [`patch`]
456/// if any patch failed, the document is left in an inconsistent state. In case of internal error
457/// resulting in panic, document might be left in inconsistent state.
458///
459/// # Example
460/// Create and patch document:
461///
462/// ```rust
463/// #[macro_use]
464/// use json_patch::{Patch, patch_unsafe};
465/// use serde_json::{from_value, json};
466///
467/// # pub fn main() {
468/// let mut doc = json!([
469///     { "name": "Andrew" },
470///     { "name": "Maxim" }
471/// ]);
472///
473/// let p: Patch = from_value(json!([
474///   { "op": "test", "path": "/0/name", "value": "Andrew" },
475///   { "op": "add", "path": "/0/happy", "value": true }
476/// ])).unwrap();
477///
478/// patch_unsafe(&mut doc, &p).unwrap();
479/// assert_eq!(doc, json!([
480///   { "name": "Andrew", "happy": true },
481///   { "name": "Maxim" }
482/// ]));
483///
484/// # }
485/// ```
486pub fn patch_unsafe(doc: &mut Value, patch: &[PatchOperation]) -> Result<(), PatchError> {
487    apply_patches(doc, patch, None)
488}
489
490/// Undoes operations performed by `apply_patches`. This is useful to recover the original document
491/// in case of an error.
492fn undo_patches(doc: &mut Value, undo_patches: &[PatchOperation]) -> Result<(), PatchError> {
493    for (operation, patch) in undo_patches.iter().enumerate().rev() {
494        match patch {
495            PatchOperation::Add(op) => {
496                add(doc, &op.path, op.value.clone())
497                    .map_err(|e| translate_error(e, operation, &op.path))?;
498            }
499            PatchOperation::Remove(op) => {
500                remove(doc, &op.path, true).map_err(|e| translate_error(e, operation, &op.path))?;
501            }
502            PatchOperation::Replace(op) => {
503                replace(doc, &op.path, op.value.clone())
504                    .map_err(|e| translate_error(e, operation, &op.path))?;
505            }
506            PatchOperation::Move(op) => {
507                mov(doc, &op.from, &op.path, true)
508                    .map_err(|e| translate_error(e, operation, &op.path))?;
509            }
510            PatchOperation::Copy(op) => {
511                copy(doc, &op.from, &op.path)
512                    .map_err(|e| translate_error(e, operation, &op.path))?;
513            }
514            _ => unreachable!(),
515        }
516    }
517
518    Ok(())
519}
520
521// Apply patches while tracking all the changes being made so they can be reverted back in case
522// subsequent patches fail. The inverse of all state changes is recorded in the `undo_stack` which
523// can be reapplied using `undo_patches` to get back to the original document.
524fn apply_patches(
525    doc: &mut Value,
526    patches: &[PatchOperation],
527    undo_stack: Option<&mut Vec<PatchOperation>>,
528) -> Result<(), PatchError> {
529    for (operation, patch) in patches.iter().enumerate() {
530        match patch {
531            PatchOperation::Add(ref op) => {
532                let prev = add(doc, &op.path, op.value.clone())
533                    .map_err(|e| translate_error(e, operation, &op.path))?;
534                if let Some(&mut ref mut undo_stack) = undo_stack {
535                    undo_stack.push(match prev {
536                        None => PatchOperation::Remove(RemoveOperation {
537                            path: op.path.clone(),
538                        }),
539                        Some(v) => PatchOperation::Add(AddOperation {
540                            path: op.path.clone(),
541                            value: v,
542                        }),
543                    })
544                }
545            }
546            PatchOperation::Remove(ref op) => {
547                let prev = remove(doc, &op.path, false)
548                    .map_err(|e| translate_error(e, operation, &op.path))?;
549                if let Some(&mut ref mut undo_stack) = undo_stack {
550                    undo_stack.push(PatchOperation::Add(AddOperation {
551                        path: op.path.clone(),
552                        value: prev,
553                    }))
554                }
555            }
556            PatchOperation::Replace(ref op) => {
557                let prev = replace(doc, &op.path, op.value.clone())
558                    .map_err(|e| translate_error(e, operation, &op.path))?;
559                if let Some(&mut ref mut undo_stack) = undo_stack {
560                    undo_stack.push(PatchOperation::Replace(ReplaceOperation {
561                        path: op.path.clone(),
562                        value: prev,
563                    }))
564                }
565            }
566            PatchOperation::Move(ref op) => {
567                let prev = mov(doc, &op.from, &op.path, false)
568                    .map_err(|e| translate_error(e, operation, &op.path))?;
569                if let Some(&mut ref mut undo_stack) = undo_stack {
570                    if let Some(prev) = prev {
571                        undo_stack.push(PatchOperation::Add(AddOperation {
572                            path: op.path.clone(),
573                            value: prev,
574                        }));
575                    }
576                    undo_stack.push(PatchOperation::Move(MoveOperation {
577                        from: op.path.clone(),
578                        path: op.from.clone(),
579                    }));
580                }
581            }
582            PatchOperation::Copy(ref op) => {
583                let prev = copy(doc, &op.from, &op.path)
584                    .map_err(|e| translate_error(e, operation, &op.path))?;
585                if let Some(&mut ref mut undo_stack) = undo_stack {
586                    undo_stack.push(match prev {
587                        None => PatchOperation::Remove(RemoveOperation {
588                            path: op.path.clone(),
589                        }),
590                        Some(v) => PatchOperation::Add(AddOperation {
591                            path: op.path.clone(),
592                            value: v,
593                        }),
594                    })
595                }
596            }
597            PatchOperation::Test(ref op) => {
598                test(doc, &op.path, &op.value)
599                    .map_err(|e| translate_error(e, operation, &op.path))?;
600            }
601        }
602    }
603
604    Ok(())
605}
606
607/// Patch provided JSON document (given as `serde_json::Value`) in place with JSON Merge Patch
608/// (RFC 7396).
609///
610/// # Example
611/// Create and patch document:
612///
613/// ```rust
614/// #[macro_use]
615/// use json_patch::merge;
616/// use serde_json::json;
617///
618/// # pub fn main() {
619/// let mut doc = json!({
620///   "title": "Goodbye!",
621///   "author" : {
622///     "givenName" : "John",
623///     "familyName" : "Doe"
624///   },
625///   "tags":[ "example", "sample" ],
626///   "content": "This will be unchanged"
627/// });
628///
629/// let patch = json!({
630///   "title": "Hello!",
631///   "phoneNumber": "+01-123-456-7890",
632///   "author": {
633///     "familyName": null
634///   },
635///   "tags": [ "example" ]
636/// });
637///
638/// merge(&mut doc, &patch);
639/// assert_eq!(doc, json!({
640///   "title": "Hello!",
641///   "author" : {
642///     "givenName" : "John"
643///   },
644///   "tags": [ "example" ],
645///   "content": "This will be unchanged",
646///   "phoneNumber": "+01-123-456-7890"
647/// }));
648/// # }
649/// ```
650pub fn merge(doc: &mut Value, patch: &Value) {
651    if !patch.is_object() {
652        *doc = patch.clone();
653        return;
654    }
655
656    if !doc.is_object() {
657        *doc = Value::Object(Map::new());
658    }
659    let map = doc.as_object_mut().unwrap();
660    for (key, value) in patch.as_object().unwrap() {
661        if value.is_null() {
662            map.remove(key.as_str());
663        } else {
664            merge(map.entry(key.as_str()).or_insert(Value::Null), value);
665        }
666    }
667}