Skip to main content

moq_json/
diff.rs

1//! Generate an [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396.html) JSON Merge Patch directly
2//! from a value, diffing it against the previously published value as it is serialized.
3//!
4//! A serde [`Serializer`] walks the new value and compares each field against the corresponding
5//! node of the old [`Value`], so unchanged scalars and subtrees cost only a comparison (no
6//! allocation) and only changed nodes are built into the patch. This avoids materializing a full
7//! `Value` tree for the new value just to diff two trees.
8
9use std::cell::Cell;
10use std::collections::HashSet;
11
12use serde::Serialize;
13use serde::ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct, Serializer};
14use serde_json::{Map, Value};
15
16/// The result of diffing a value into an RFC 7396 merge patch.
17pub struct Diff {
18	/// A merge patch that transforms the old value into the new one.
19	pub patch: Value,
20
21	/// Set when the change can't be faithfully expressed as a merge patch, so the caller should
22	/// publish a full snapshot instead. This happens when a value is set to JSON null, which merge
23	/// patch reads as a key deletion, or when the root is not an object. Arrays are fine: merge patch
24	/// replaces them wholesale, which is still typically smaller than a full snapshot.
25	pub forced_snapshot: bool,
26}
27
28/// Generate an RFC 7396 merge patch transforming `old` into `new`.
29///
30/// Only object roots produce a recursive patch; any other root forces a snapshot. A merge patch that
31/// would delete a key it shouldn't (a value genuinely set to null) also forces a snapshot.
32pub fn diff<T: Serialize>(old: &Value, new: &T) -> Diff {
33	let forced = Cell::new(false);
34	let node = new.serialize(Differ {
35		baseline: old,
36		forced: &forced,
37	});
38
39	match node {
40		// No field differed: an empty patch. A null somewhere may still have forced a snapshot.
41		Ok(Node::Same) => Diff {
42			patch: Value::Object(Map::new()),
43			forced_snapshot: forced.get(),
44		},
45		// A non-object patch (or non-object baseline) can't be a recursive merge patch, so force a
46		// snapshot for non-object roots.
47		Ok(Node::Diff(patch)) => {
48			let non_object_root = !patch.is_object() || !old.is_object();
49			Diff {
50				patch,
51				forced_snapshot: forced.get() || non_object_root,
52			}
53		}
54		// A value that isn't representable as JSON (e.g. a non-string map key) can't be diffed. Fall
55		// back to a snapshot; the caller's own serialization surfaces the real error if there is one.
56		Err(_) => Diff {
57			patch: Value::Object(Map::new()),
58			forced_snapshot: true,
59		},
60	}
61}
62
63/// One node's verdict from the diffing serializer.
64enum Node {
65	/// Equal to the baseline; nothing to emit.
66	Same,
67	/// Differs; the new value to splice into the patch.
68	Diff(Value),
69}
70
71const NULL: Value = Value::Null;
72
73/// Serializer that diffs `T` against `baseline` and yields a merge patch. `forced` is set if a
74/// genuine null is emitted (merge patch can't represent it, so the caller must snapshot).
75#[derive(Copy, Clone)]
76struct Differ<'a> {
77	baseline: &'a Value,
78	forced: &'a Cell<bool>,
79}
80
81impl<'a> Differ<'a> {
82	/// The baseline child for `key` and whether the baseline actually had that key (a missing key
83	/// means the field is an addition, which `MapDiff` uses to keep deletion detection cheap).
84	fn child(&self, key: &str) -> (Differ<'a>, bool) {
85		let (baseline, existed) = match self.baseline {
86			Value::Object(m) => match m.get(key) {
87				Some(value) => (value, true),
88				None => (&NULL, false),
89			},
90			_ => (&NULL, false),
91		};
92		(
93			Differ {
94				baseline,
95				forced: self.forced,
96			},
97			existed,
98		)
99	}
100
101	/// Compare a freshly built scalar/array against the baseline, flagging emitted nulls as forced.
102	fn scalar(self, value: Value) -> Result<Node, Error> {
103		if self.baseline == &value {
104			Ok(Node::Same)
105		} else {
106			// A genuine null can't be stored: merge patch would read it as a key deletion.
107			if value.is_null() {
108				self.forced.set(true);
109			}
110			Ok(Node::Diff(value))
111		}
112	}
113}
114
115/// Minimal serde error for the diffing serializer. JSON-shaped data never produces one in practice.
116#[derive(Debug)]
117struct Error(String);
118
119impl std::fmt::Display for Error {
120	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121		f.write_str(&self.0)
122	}
123}
124
125impl std::error::Error for Error {}
126
127impl serde::ser::Error for Error {
128	fn custom<M: std::fmt::Display>(msg: M) -> Self {
129		Error(msg.to_string())
130	}
131}
132
133/// Build a Value with no diffing (used for array elements, which merge patch replaces wholesale).
134fn to_plain<T: Serialize + ?Sized>(value: &T) -> Result<Value, Error> {
135	serde_json::to_value(value).map_err(|e| Error(e.to_string()))
136}
137
138impl<'a> Serializer for Differ<'a> {
139	type Ok = Node;
140	type Error = Error;
141	type SerializeSeq = SeqDiff<'a>;
142	type SerializeTuple = SeqDiff<'a>;
143	type SerializeTupleStruct = SeqDiff<'a>;
144	type SerializeTupleVariant = VariantSeq<'a>;
145	type SerializeMap = MapDiff<'a>;
146	type SerializeStruct = MapDiff<'a>;
147	type SerializeStructVariant = VariantMap<'a>;
148
149	fn serialize_bool(self, v: bool) -> Result<Node, Error> {
150		self.scalar(Value::Bool(v))
151	}
152	fn serialize_i8(self, v: i8) -> Result<Node, Error> {
153		self.scalar(Value::from(v))
154	}
155	fn serialize_i16(self, v: i16) -> Result<Node, Error> {
156		self.scalar(Value::from(v))
157	}
158	fn serialize_i32(self, v: i32) -> Result<Node, Error> {
159		self.scalar(Value::from(v))
160	}
161	fn serialize_i64(self, v: i64) -> Result<Node, Error> {
162		self.scalar(Value::from(v))
163	}
164	fn serialize_i128(self, v: i128) -> Result<Node, Error> {
165		self.scalar(to_plain(&v)?)
166	}
167	fn serialize_u8(self, v: u8) -> Result<Node, Error> {
168		self.scalar(Value::from(v))
169	}
170	fn serialize_u16(self, v: u16) -> Result<Node, Error> {
171		self.scalar(Value::from(v))
172	}
173	fn serialize_u32(self, v: u32) -> Result<Node, Error> {
174		self.scalar(Value::from(v))
175	}
176	fn serialize_u64(self, v: u64) -> Result<Node, Error> {
177		self.scalar(Value::from(v))
178	}
179	fn serialize_u128(self, v: u128) -> Result<Node, Error> {
180		self.scalar(to_plain(&v)?)
181	}
182	fn serialize_f32(self, v: f32) -> Result<Node, Error> {
183		self.scalar(Value::from(v))
184	}
185	fn serialize_f64(self, v: f64) -> Result<Node, Error> {
186		self.scalar(Value::from(v))
187	}
188	fn serialize_char(self, v: char) -> Result<Node, Error> {
189		self.scalar(Value::from(v.to_string()))
190	}
191	fn serialize_str(self, v: &str) -> Result<Node, Error> {
192		// Strings are the common churn-free field, so compare against the baseline without allocating a
193		// `Value::String` on the unchanged path.
194		if matches!(self.baseline, Value::String(b) if b == v) {
195			Ok(Node::Same)
196		} else {
197			Ok(Node::Diff(Value::from(v)))
198		}
199	}
200	fn serialize_bytes(self, v: &[u8]) -> Result<Node, Error> {
201		self.scalar(to_plain(v)?)
202	}
203	fn serialize_none(self) -> Result<Node, Error> {
204		self.scalar(Value::Null)
205	}
206	fn serialize_some<T: Serialize + ?Sized>(self, value: &T) -> Result<Node, Error> {
207		value.serialize(self)
208	}
209	fn serialize_unit(self) -> Result<Node, Error> {
210		self.scalar(Value::Null)
211	}
212	fn serialize_unit_struct(self, _name: &'static str) -> Result<Node, Error> {
213		self.scalar(Value::Null)
214	}
215	fn serialize_unit_variant(self, _name: &'static str, _idx: u32, variant: &'static str) -> Result<Node, Error> {
216		self.scalar(Value::from(variant))
217	}
218	fn serialize_newtype_struct<T: Serialize + ?Sized>(self, _name: &'static str, value: &T) -> Result<Node, Error> {
219		value.serialize(self)
220	}
221	fn serialize_newtype_variant<T: Serialize + ?Sized>(
222		self,
223		_name: &'static str,
224		_idx: u32,
225		variant: &'static str,
226		value: &T,
227	) -> Result<Node, Error> {
228		// An externally-tagged newtype variant serializes as `{ "Variant": value }`. Diff that object
229		// against the baseline like any other object, so the tag is preserved and the payload diffs
230		// minimally (a variant switch deletes the old tag and adds the new one).
231		variant_object(variant, to_plain(value)?).serialize(self)
232	}
233	fn serialize_seq(self, len: Option<usize>) -> Result<SeqDiff<'a>, Error> {
234		Ok(SeqDiff {
235			differ: self,
236			items: Vec::with_capacity(len.unwrap_or(0)),
237		})
238	}
239	fn serialize_tuple(self, len: usize) -> Result<SeqDiff<'a>, Error> {
240		self.serialize_seq(Some(len))
241	}
242	fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result<SeqDiff<'a>, Error> {
243		self.serialize_seq(Some(len))
244	}
245	fn serialize_tuple_variant(
246		self,
247		_name: &'static str,
248		_idx: u32,
249		variant: &'static str,
250		len: usize,
251	) -> Result<VariantSeq<'a>, Error> {
252		// A tuple variant serializes as `{ "Variant": [..] }`, replaced wholesale.
253		Ok(VariantSeq {
254			differ: self,
255			variant,
256			items: Vec::with_capacity(len),
257		})
258	}
259	fn serialize_map(self, _len: Option<usize>) -> Result<MapDiff<'a>, Error> {
260		Ok(MapDiff {
261			differ: self,
262			patch: Map::new(),
263			seen: Vec::new(),
264			added_key: false,
265			pending_key: None,
266		})
267	}
268	fn serialize_struct(self, _name: &'static str, len: usize) -> Result<MapDiff<'a>, Error> {
269		self.serialize_map(Some(len))
270	}
271	fn serialize_struct_variant(
272		self,
273		_name: &'static str,
274		_idx: u32,
275		variant: &'static str,
276		_len: usize,
277	) -> Result<VariantMap<'a>, Error> {
278		// A struct variant serializes as `{ "Variant": { .. } }`, replaced wholesale.
279		Ok(VariantMap {
280			differ: self,
281			variant,
282			fields: Map::new(),
283		})
284	}
285}
286
287/// Wrap a value as an externally-tagged variant object `{ variant: value }`.
288fn variant_object(variant: &str, value: Value) -> Value {
289	let mut object = Map::new();
290	object.insert(variant.to_owned(), value);
291	Value::Object(object)
292}
293
294/// Collects a tuple variant's fields into `{ variant: [..] }`, then diffs it against the baseline.
295struct VariantSeq<'a> {
296	differ: Differ<'a>,
297	variant: &'static str,
298	items: Vec<Value>,
299}
300
301impl serde::ser::SerializeTupleVariant for VariantSeq<'_> {
302	type Ok = Node;
303	type Error = Error;
304	fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
305		self.items.push(to_plain(value)?);
306		Ok(())
307	}
308	fn end(self) -> Result<Node, Error> {
309		variant_object(self.variant, Value::Array(self.items)).serialize(self.differ)
310	}
311}
312
313/// Collects a struct variant's fields into `{ variant: { .. } }`, then diffs it against the baseline.
314struct VariantMap<'a> {
315	differ: Differ<'a>,
316	variant: &'static str,
317	fields: Map<String, Value>,
318}
319
320impl serde::ser::SerializeStructVariant for VariantMap<'_> {
321	type Ok = Node;
322	type Error = Error;
323	fn serialize_field<T: Serialize + ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), Error> {
324		self.fields.insert(key.to_owned(), to_plain(value)?);
325		Ok(())
326	}
327	fn end(self) -> Result<Node, Error> {
328		variant_object(self.variant, Value::Object(self.fields)).serialize(self.differ)
329	}
330}
331
332/// Arrays are replaced wholesale by merge patch, so this builds the full new array and compares it
333/// to the baseline in one shot.
334struct SeqDiff<'a> {
335	differ: Differ<'a>,
336	items: Vec<Value>,
337}
338
339impl SerializeSeq for SeqDiff<'_> {
340	type Ok = Node;
341	type Error = Error;
342	fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
343		self.items.push(to_plain(value)?);
344		Ok(())
345	}
346	fn end(self) -> Result<Node, Error> {
347		self.differ.scalar(Value::Array(self.items))
348	}
349}
350
351impl serde::ser::SerializeTuple for SeqDiff<'_> {
352	type Ok = Node;
353	type Error = Error;
354	fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
355		SerializeSeq::serialize_element(self, value)
356	}
357	fn end(self) -> Result<Node, Error> {
358		SerializeSeq::end(self)
359	}
360}
361
362impl serde::ser::SerializeTupleStruct for SeqDiff<'_> {
363	type Ok = Node;
364	type Error = Error;
365	fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
366		SerializeSeq::serialize_element(self, value)
367	}
368	fn end(self) -> Result<Node, Error> {
369		SerializeSeq::end(self)
370	}
371}
372
373/// Objects recurse: each entry diffs against the baseline's child, and only changed entries land in
374/// the patch. Keys present in the baseline but absent now become explicit null deletions.
375struct MapDiff<'a> {
376	differ: Differ<'a>,
377	patch: Map<String, Value>,
378	seen: Vec<String>,
379	// Set when a field's key was absent from the baseline. Lets `finish` skip the deletion scan when
380	// the new keys are exactly the baseline keys (the common, churn-free case).
381	added_key: bool,
382	pending_key: Option<String>,
383}
384
385impl MapDiff<'_> {
386	fn entry(&mut self, key: String, existed: bool, node: Node) {
387		self.added_key |= !existed;
388		if let Node::Diff(value) = node {
389			self.patch.insert(key.clone(), value);
390		}
391		self.seen.push(key);
392	}
393
394	fn finish(self) -> Result<Node, Error> {
395		let mut patch = self.patch;
396		if let Value::Object(base) = self.differ.baseline {
397			// A deletion is only possible if some key was added or the counts differ. Otherwise the new
398			// keys are exactly the baseline keys, so there's nothing to delete and we skip the scan,
399			// keeping the common path O(1) rather than O(n^2). A removed key is a clean delete (explicit
400			// null), and unlike a value set to null it does not force a snapshot.
401			if self.added_key || self.seen.len() != base.len() {
402				let seen: HashSet<&str> = self.seen.iter().map(String::as_str).collect();
403				for key in base.keys() {
404					if !seen.contains(key.as_str()) {
405						patch.insert(key.clone(), Value::Null);
406					}
407				}
408			}
409		}
410		if patch.is_empty() {
411			Ok(Node::Same)
412		} else {
413			Ok(Node::Diff(Value::Object(patch)))
414		}
415	}
416}
417
418impl SerializeMap for MapDiff<'_> {
419	type Ok = Node;
420	type Error = Error;
421	fn serialize_key<T: Serialize + ?Sized>(&mut self, key: &T) -> Result<(), Error> {
422		// Extract the key string in a single allocation (no intermediate Value).
423		self.pending_key = Some(key.serialize(KeySer)?);
424		Ok(())
425	}
426	fn serialize_value<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
427		let key = self.pending_key.take().expect("serialize_key precedes serialize_value");
428		let (child, existed) = self.differ.child(&key);
429		let node = value.serialize(child)?;
430		self.entry(key, existed, node);
431		Ok(())
432	}
433	fn end(self) -> Result<Node, Error> {
434		self.finish()
435	}
436}
437
438impl SerializeStruct for MapDiff<'_> {
439	type Ok = Node;
440	type Error = Error;
441	fn serialize_field<T: Serialize + ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), Error> {
442		let (child, existed) = self.differ.child(key);
443		let node = value.serialize(child)?;
444		self.entry(key.to_owned(), existed, node);
445		Ok(())
446	}
447	// A field skipped via `skip_serializing_if` is simply never offered here, so it stays out of `seen`
448	// and `finish` emits it as a null deletion if the baseline had it (the default `skip_field` suffices).
449	fn end(self) -> Result<Node, Error> {
450		self.finish()
451	}
452}
453
454/// Serializes a map key to its `String`, the only form JSON object keys take. Anything else is an
455/// error, mirroring `serde_json`'s own key handling.
456struct KeySer;
457
458impl Serializer for KeySer {
459	type Ok = String;
460	type Error = Error;
461	type SerializeSeq = Impossible<String, Error>;
462	type SerializeTuple = Impossible<String, Error>;
463	type SerializeTupleStruct = Impossible<String, Error>;
464	type SerializeTupleVariant = Impossible<String, Error>;
465	type SerializeMap = Impossible<String, Error>;
466	type SerializeStruct = Impossible<String, Error>;
467	type SerializeStructVariant = Impossible<String, Error>;
468
469	fn serialize_str(self, v: &str) -> Result<String, Error> {
470		Ok(v.to_owned())
471	}
472	fn serialize_char(self, v: char) -> Result<String, Error> {
473		Ok(v.to_string())
474	}
475	fn serialize_bool(self, v: bool) -> Result<String, Error> {
476		Ok(v.to_string())
477	}
478	fn serialize_i8(self, v: i8) -> Result<String, Error> {
479		Ok(v.to_string())
480	}
481	fn serialize_i16(self, v: i16) -> Result<String, Error> {
482		Ok(v.to_string())
483	}
484	fn serialize_i32(self, v: i32) -> Result<String, Error> {
485		Ok(v.to_string())
486	}
487	fn serialize_i64(self, v: i64) -> Result<String, Error> {
488		Ok(v.to_string())
489	}
490	fn serialize_u8(self, v: u8) -> Result<String, Error> {
491		Ok(v.to_string())
492	}
493	fn serialize_u16(self, v: u16) -> Result<String, Error> {
494		Ok(v.to_string())
495	}
496	fn serialize_u32(self, v: u32) -> Result<String, Error> {
497		Ok(v.to_string())
498	}
499	fn serialize_u64(self, v: u64) -> Result<String, Error> {
500		Ok(v.to_string())
501	}
502	fn serialize_unit_variant(self, _name: &'static str, _idx: u32, variant: &'static str) -> Result<String, Error> {
503		Ok(variant.to_owned())
504	}
505	fn serialize_newtype_struct<T: Serialize + ?Sized>(self, _name: &'static str, value: &T) -> Result<String, Error> {
506		value.serialize(self)
507	}
508	fn serialize_some<T: Serialize + ?Sized>(self, value: &T) -> Result<String, Error> {
509		value.serialize(self)
510	}
511	fn serialize_f32(self, _v: f32) -> Result<String, Error> {
512		Err(Error("float map key".into()))
513	}
514	fn serialize_f64(self, _v: f64) -> Result<String, Error> {
515		Err(Error("float map key".into()))
516	}
517	fn serialize_bytes(self, _v: &[u8]) -> Result<String, Error> {
518		Err(Error("bytes map key".into()))
519	}
520	fn serialize_none(self) -> Result<String, Error> {
521		Err(Error("null map key".into()))
522	}
523	fn serialize_unit(self) -> Result<String, Error> {
524		Err(Error("unit map key".into()))
525	}
526	fn serialize_unit_struct(self, _name: &'static str) -> Result<String, Error> {
527		Err(Error("unit struct map key".into()))
528	}
529	fn serialize_newtype_variant<T: Serialize + ?Sized>(
530		self,
531		_name: &'static str,
532		_idx: u32,
533		_variant: &'static str,
534		_value: &T,
535	) -> Result<String, Error> {
536		Err(Error("newtype variant map key".into()))
537	}
538	fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
539		Err(Error("seq map key".into()))
540	}
541	fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Error> {
542		Err(Error("tuple map key".into()))
543	}
544	fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeTupleStruct, Error> {
545		Err(Error("tuple struct map key".into()))
546	}
547	fn serialize_tuple_variant(
548		self,
549		_name: &'static str,
550		_idx: u32,
551		_variant: &'static str,
552		_len: usize,
553	) -> Result<Self::SerializeTupleVariant, Error> {
554		Err(Error("tuple variant map key".into()))
555	}
556	fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
557		Err(Error("map map key".into()))
558	}
559	fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct, Error> {
560		Err(Error("struct map key".into()))
561	}
562	fn serialize_struct_variant(
563		self,
564		_name: &'static str,
565		_idx: u32,
566		_variant: &'static str,
567		_len: usize,
568	) -> Result<Self::SerializeStructVariant, Error> {
569		Err(Error("struct variant map key".into()))
570	}
571}
572
573#[cfg(test)]
574mod test {
575	use super::*;
576	use serde_json::json;
577
578	/// A straightforward Value-vs-Value merge-patch diff, used only as a test oracle: the production
579	/// `diff` (the serializer) must agree with it on every case.
580	fn reference(old: &Value, new: &Value) -> Diff {
581		fn objects(
582			old: &Map<String, Value>,
583			new: &Map<String, Value>,
584			patch: &mut Map<String, Value>,
585			forced: &mut bool,
586		) {
587			for key in old.keys() {
588				if !new.contains_key(key) {
589					patch.insert(key.clone(), Value::Null);
590				}
591			}
592			for (key, new_val) in new {
593				let old_val = old.get(key);
594				if old_val == Some(new_val) {
595					continue;
596				}
597				if let (Some(Value::Object(old_obj)), Value::Object(new_obj)) = (old_val, new_val) {
598					let mut sub = Map::new();
599					objects(old_obj, new_obj, &mut sub, forced);
600					if !sub.is_empty() {
601						patch.insert(key.clone(), Value::Object(sub));
602					}
603					continue;
604				}
605				if new_val.is_null() {
606					*forced = true;
607				}
608				patch.insert(key.clone(), new_val.clone());
609			}
610		}
611
612		if let (Value::Object(old_obj), Value::Object(new_obj)) = (old, new) {
613			let mut patch = Map::new();
614			let mut forced = false;
615			objects(old_obj, new_obj, &mut patch, &mut forced);
616			Diff {
617				patch: Value::Object(patch),
618				forced_snapshot: forced,
619			}
620		} else {
621			Diff {
622				patch: new.clone(),
623				forced_snapshot: true,
624			}
625		}
626	}
627
628	/// The serializer must produce the same patch and forced flag as the reference oracle, and (when
629	/// not forced) applying the patch to `old` must reproduce `new`.
630	fn check(old: Value, new: Value) {
631		let want = reference(&old, &new);
632		let got = diff(&old, &new);
633		assert_eq!(got.patch, want.patch, "patch mismatch for {old} -> {new}");
634		assert_eq!(
635			got.forced_snapshot, want.forced_snapshot,
636			"forced mismatch for {old} -> {new}"
637		);
638		if !got.forced_snapshot {
639			let mut applied = old.clone();
640			json_patch::merge(&mut applied, &got.patch);
641			assert_eq!(applied, new, "patch did not roundtrip for {old} -> {new}");
642		}
643	}
644
645	#[test]
646	fn changed_scalar() {
647		check(json!({ "a": 1, "b": 2 }), json!({ "a": 1, "b": 3 }));
648	}
649
650	#[test]
651	fn added_key() {
652		let result = diff(&json!({ "a": 1 }), &json!({ "a": 1, "b": 2 }));
653		assert!(!result.forced_snapshot);
654		assert_eq!(result.patch, json!({ "b": 2 }));
655		check(json!({ "a": 1 }), json!({ "a": 1, "b": 2 }));
656	}
657
658	#[test]
659	fn removed_key_is_null() {
660		let result = diff(&json!({ "a": 1, "b": 2 }), &json!({ "a": 1 }));
661		assert!(!result.forced_snapshot, "removing a key is a clean delete");
662		assert_eq!(result.patch, json!({ "b": null }));
663		check(json!({ "a": 1, "b": 2 }), json!({ "a": 1 }));
664	}
665
666	#[test]
667	fn nested_object_only_includes_changed_keys() {
668		let result = diff(&json!({ "o": { "x": 1, "y": 2 } }), &json!({ "o": { "x": 1, "y": 9 } }));
669		assert!(!result.forced_snapshot);
670		assert_eq!(result.patch, json!({ "o": { "y": 9 } }));
671		check(json!({ "o": { "x": 1, "y": 2 } }), json!({ "o": { "x": 1, "y": 9 } }));
672	}
673
674	#[test]
675	fn unchanged_object_is_empty_patch() {
676		let result = diff(&json!({ "a": 1, "o": { "x": 1 } }), &json!({ "a": 1, "o": { "x": 1 } }));
677		assert!(!result.forced_snapshot);
678		assert_eq!(result.patch, json!({}));
679	}
680
681	#[test]
682	fn changed_array_is_wholesale_delta() {
683		let result = diff(&json!({ "a": [1, 2] }), &json!({ "a": [1, 2, 3] }));
684		assert!(!result.forced_snapshot);
685		assert_eq!(result.patch, json!({ "a": [1, 2, 3] }));
686		check(json!({ "a": [1, 2] }), json!({ "a": [1, 2, 3] }));
687	}
688
689	#[test]
690	fn unchanged_array_is_pruned() {
691		let result = diff(&json!({ "a": [1, 2, 3], "b": 1 }), &json!({ "a": [1, 2, 3], "b": 2 }));
692		assert_eq!(
693			result.patch,
694			json!({ "b": 2 }),
695			"an unchanged array stays out of the patch"
696		);
697	}
698
699	#[test]
700	fn added_array_is_delta() {
701		check(json!({ "a": 1 }), json!({ "a": 1, "b": [1] }));
702	}
703
704	#[test]
705	fn nested_array_is_delta() {
706		check(json!({ "o": { "x": 1 } }), json!({ "o": { "x": 1, "list": [1] } }));
707	}
708
709	#[test]
710	fn array_of_objects_replaces_wholesale() {
711		check(
712			json!({ "items": [{ "id": 1, "v": 1 }, { "id": 2, "v": 2 }] }),
713			json!({ "items": [{ "id": 1, "v": 9 }, { "id": 2, "v": 2 }] }),
714		);
715	}
716
717	#[test]
718	fn set_to_null_forces_snapshot() {
719		// A genuine null value can't be represented: merge patch would delete the key.
720		let result = diff(&json!({ "a": 1 }), &json!({ "a": null }));
721		assert!(result.forced_snapshot);
722		assert!(reference(&json!({ "a": 1 }), &json!({ "a": null })).forced_snapshot);
723	}
724
725	#[test]
726	fn nested_null_forces_snapshot() {
727		let old = json!({ "o": { "x": 1 } });
728		let new = json!({ "o": { "x": null } });
729		assert!(diff(&old, &new).forced_snapshot);
730		assert_eq!(diff(&old, &new).forced_snapshot, reference(&old, &new).forced_snapshot);
731	}
732
733	#[test]
734	fn replacing_object_with_scalar() {
735		check(json!({ "a": { "x": 1 } }), json!({ "a": 5 }));
736	}
737
738	#[test]
739	fn replacing_scalar_with_object() {
740		check(json!({ "a": 5 }), json!({ "a": { "x": 1 } }));
741	}
742
743	#[test]
744	fn non_object_root_forces_snapshot() {
745		let result = diff(&json!(1), &json!(2));
746		assert!(result.forced_snapshot);
747		assert_eq!(result.patch, json!(2));
748	}
749
750	#[test]
751	fn array_root_forces_snapshot() {
752		let result = diff(&json!([1, 2]), &json!([1, 2, 3]));
753		assert!(result.forced_snapshot);
754		assert_eq!(result.patch, json!([1, 2, 3]));
755	}
756
757	#[test]
758	fn unchanged_scalar_root_is_not_forced() {
759		// An equal non-object root is a no-op (empty patch), matching the producer's dedup.
760		let result = diff(&json!(7), &json!(7));
761		assert!(!result.forced_snapshot);
762		assert_eq!(result.patch, json!({}));
763	}
764
765	#[test]
766	fn floats_and_bools_and_strings() {
767		check(
768			json!({ "f": 1.5, "b": true, "s": "hi" }),
769			json!({ "f": 2.5, "b": false, "s": "bye" }),
770		);
771	}
772
773	// ---- Typed structs (the serializer's whole point: diff `T` without building its Value) ----
774
775	#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Debug)]
776	struct Doc {
777		#[serde(skip_serializing_if = "Option::is_none")]
778		video: Option<String>,
779		#[serde(skip_serializing_if = "Option::is_none")]
780		scte35: Option<u32>,
781		count: u64,
782		tags: Vec<String>,
783	}
784
785	/// Diff a typed struct against the Value of a prior typed struct; the patch must roundtrip.
786	fn check_struct(old: &Doc, new: &Doc) {
787		let old_value = serde_json::to_value(old).unwrap();
788		let result = diff(&old_value, new);
789
790		// Cross-check against the oracle fed the equivalent Values.
791		let new_value = serde_json::to_value(new).unwrap();
792		let want = reference(&old_value, &new_value);
793		assert_eq!(result.patch, want.patch, "struct patch differs from oracle");
794		assert_eq!(result.forced_snapshot, want.forced_snapshot);
795
796		if !result.forced_snapshot {
797			let mut applied = old_value;
798			json_patch::merge(&mut applied, &result.patch);
799			assert_eq!(applied, new_value, "struct patch did not roundtrip");
800		}
801	}
802
803	#[test]
804	fn struct_field_change() {
805		check_struct(
806			&Doc {
807				count: 1,
808				tags: vec!["a".into()],
809				..Default::default()
810			},
811			&Doc {
812				count: 2,
813				tags: vec!["a".into()],
814				..Default::default()
815			},
816		);
817	}
818
819	#[test]
820	fn struct_option_some_to_none_is_deletion() {
821		// A skipped field (None) must become a null deletion of the previously-present key.
822		let result = diff(
823			&serde_json::to_value(Doc {
824				video: Some("v1".into()),
825				count: 1,
826				..Default::default()
827			})
828			.unwrap(),
829			&Doc {
830				video: None,
831				count: 1,
832				..Default::default()
833			},
834		);
835		assert!(!result.forced_snapshot, "deleting a skipped key is clean");
836		assert_eq!(result.patch, json!({ "video": null }));
837		check_struct(
838			&Doc {
839				video: Some("v1".into()),
840				count: 1,
841				..Default::default()
842			},
843			&Doc {
844				video: None,
845				count: 1,
846				..Default::default()
847			},
848		);
849	}
850
851	#[test]
852	fn struct_option_none_to_some_is_addition() {
853		check_struct(
854			&Doc {
855				count: 1,
856				..Default::default()
857			},
858			&Doc {
859				scte35: Some(42),
860				count: 1,
861				..Default::default()
862			},
863		);
864	}
865
866	#[test]
867	fn struct_unchanged_is_empty_patch() {
868		let doc = Doc {
869			video: Some("v".into()),
870			scte35: Some(1),
871			count: 7,
872			tags: vec!["x".into(), "y".into()],
873		};
874		let result = diff(&serde_json::to_value(&doc).unwrap(), &doc);
875		assert!(!result.forced_snapshot);
876		assert_eq!(result.patch, json!({}));
877	}
878
879	#[test]
880	fn struct_vec_changes_wholesale() {
881		check_struct(
882			&Doc {
883				count: 1,
884				tags: vec!["a".into(), "b".into()],
885				..Default::default()
886			},
887			&Doc {
888				count: 1,
889				tags: vec!["a".into(), "c".into()],
890				..Default::default()
891			},
892		);
893	}
894
895	#[derive(serde::Serialize)]
896	struct Nested {
897		inner: Inner,
898		name: String,
899	}
900	#[derive(serde::Serialize)]
901	struct Inner {
902		a: u32,
903		b: u32,
904	}
905
906	#[test]
907	fn nested_struct_only_changed_field() {
908		let old = serde_json::to_value(Nested {
909			inner: Inner { a: 1, b: 2 },
910			name: "n".into(),
911		})
912		.unwrap();
913		let new = Nested {
914			inner: Inner { a: 1, b: 9 },
915			name: "n".into(),
916		};
917		let result = diff(&old, &new);
918		assert_eq!(result.patch, json!({ "inner": { "b": 9 } }));
919		assert!(!result.forced_snapshot);
920	}
921
922	#[derive(serde::Serialize)]
923	enum Tag {
924		Active,
925		Idle,
926	}
927
928	#[derive(serde::Serialize)]
929	struct Stated {
930		state: Tag,
931		seq: u32,
932	}
933
934	#[test]
935	fn unit_enum_variant_is_string() {
936		// Externally-tagged unit variants serialize as the variant name string.
937		let old = serde_json::to_value(Stated {
938			state: Tag::Active,
939			seq: 1,
940		})
941		.unwrap();
942		assert_eq!(old, json!({ "state": "Active", "seq": 1 }));
943		let result = diff(
944			&old,
945			&Stated {
946				state: Tag::Idle,
947				seq: 1,
948			},
949		);
950		assert!(!result.forced_snapshot);
951		assert_eq!(result.patch, json!({ "state": "Idle" }));
952	}
953
954	/// Diff a typed value against the Value of a prior value: the patch must match the oracle fed the
955	/// equivalent Values, and roundtrip.
956	fn check_typed<T: Serialize>(old: &Value, new: &T) {
957		let new_value = serde_json::to_value(new).unwrap();
958		let want = reference(old, &new_value);
959		let got = diff(old, new);
960		assert_eq!(got.patch, want.patch, "patch differs from oracle");
961		assert_eq!(got.forced_snapshot, want.forced_snapshot, "forced differs from oracle");
962		if !got.forced_snapshot {
963			let mut applied = old.clone();
964			json_patch::merge(&mut applied, &got.patch);
965			assert_eq!(applied, new_value, "patch did not roundtrip");
966		}
967	}
968
969	#[derive(serde::Serialize)]
970	enum Payload {
971		Newtype(u32),
972		Tuple(u32, String),
973		Struct { x: u32, y: u32 },
974	}
975
976	#[derive(serde::Serialize)]
977	struct Holder {
978		payload: Payload,
979		seq: u32,
980	}
981
982	#[test]
983	fn newtype_variant_keeps_its_tag() {
984		// Regression: a newtype variant must serialize as `{ "Newtype": v }`, not collapse to `v`.
985		let old = serde_json::to_value(Holder {
986			payload: Payload::Newtype(1),
987			seq: 0,
988		})
989		.unwrap();
990		assert_eq!(old, json!({ "payload": { "Newtype": 1 }, "seq": 0 }));
991		let result = diff(
992			&old,
993			&Holder {
994				payload: Payload::Newtype(2),
995				seq: 0,
996			},
997		);
998		assert_eq!(result.patch, json!({ "payload": { "Newtype": 2 } }));
999		check_typed(
1000			&old,
1001			&Holder {
1002				payload: Payload::Newtype(2),
1003				seq: 0,
1004			},
1005		);
1006	}
1007
1008	#[test]
1009	fn tuple_variant_keeps_its_tag() {
1010		let old = serde_json::to_value(Holder {
1011			payload: Payload::Tuple(1, "a".into()),
1012			seq: 0,
1013		})
1014		.unwrap();
1015		assert_eq!(old, json!({ "payload": { "Tuple": [1, "a"] }, "seq": 0 }));
1016		check_typed(
1017			&old,
1018			&Holder {
1019				payload: Payload::Tuple(2, "a".into()),
1020				seq: 0,
1021			},
1022		);
1023	}
1024
1025	#[test]
1026	fn struct_variant_keeps_its_tag() {
1027		let old = serde_json::to_value(Holder {
1028			payload: Payload::Struct { x: 1, y: 2 },
1029			seq: 0,
1030		})
1031		.unwrap();
1032		assert_eq!(old, json!({ "payload": { "Struct": { "x": 1, "y": 2 } }, "seq": 0 }));
1033		check_typed(
1034			&old,
1035			&Holder {
1036				payload: Payload::Struct { x: 1, y: 9 },
1037				seq: 0,
1038			},
1039		);
1040	}
1041
1042	#[derive(serde::Deserialize)]
1043	struct Vector {
1044		name: String,
1045		old: Value,
1046		new: Value,
1047		forced: bool,
1048		patch: Option<Value>,
1049	}
1050
1051	/// Shared cross-impl fixture: the TS suite (js/json) asserts the same vectors so both
1052	/// implementations agree on every snapshot/delta decision and patch shape.
1053	#[test]
1054	fn golden_vectors() {
1055		let vectors: Vec<Vector> = serde_json::from_str(include_str!("../tests/vectors.json")).unwrap();
1056		for case in vectors {
1057			let result = diff(&case.old, &case.new);
1058			assert_eq!(result.forced_snapshot, case.forced, "{}: forced_snapshot", case.name);
1059
1060			if let Some(expected) = case.patch {
1061				assert_eq!(result.patch, expected, "{}: patch", case.name);
1062				let mut applied = case.old.clone();
1063				json_patch::merge(&mut applied, &result.patch);
1064				assert_eq!(applied, case.new, "{}: roundtrip", case.name);
1065			}
1066		}
1067	}
1068
1069	/// Exercise the diff over a sequence of evolving documents, asserting agreement with the oracle
1070	/// and full roundtrip at every step (the way the producer applies deltas).
1071	#[test]
1072	fn evolving_document_matches_oracle() {
1073		let mut docs = Vec::new();
1074		for tick in 0u64..40 {
1075			docs.push(json!({
1076				"id": "device-1",
1077				"static": { "model": "x", "tags": ["a", "b", "c"] },
1078				"counters": { "n": tick, "errors": tick / 10 },
1079				"reading": (tick as f64 * 0.5),
1080				"flags": { "online": tick % 2 == 0, "charging": tick % 3 == 0 },
1081				"list": [tick, tick + 1],
1082			}));
1083		}
1084		for pair in docs.windows(2) {
1085			check(pair[0].clone(), pair[1].clone());
1086		}
1087	}
1088}