Skip to content

Commit 43a6132

Browse files
committed
ObjectMatcher should not relay on generic tuple type but rather has its own internal MapEntry class
1 parent c98d68e commit 43a6132

File tree

6 files changed

+50
-25
lines changed

6 files changed

+50
-25
lines changed

core/src/main/scala/com/softwaremill/diffx/Diff.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package com.softwaremill.diffx
2+
import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry}
23
import com.softwaremill.diffx.generic.{DiffMagnoliaDerivation, MagnoliaDerivedMacro}
34
import com.softwaremill.diffx.instances._
45

@@ -52,7 +53,7 @@ object Diff extends MiddlePriorityDiff with TupleInstances {
5253
implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit
5354
dv: Diff[V],
5455
dk: Diff[K],
55-
matcher: ObjectMatcher[(K, V)]
56+
matcher: ObjectMatcher[MapEntry[K, V]]
5657
): Diff[C[K, V]] = new DiffForMap[K, V, C](matcher, dk, dv)
5758
implicit def diffForOptional[T](implicit ddt: Diff[T]): Diff[Option[T]] = new DiffForOption[T](ddt)
5859
implicit def diffForSet[T, C[W] <: scala.collection.Set[W]](implicit
@@ -67,7 +68,7 @@ trait MiddlePriorityDiff extends DiffMagnoliaDerivation with LowPriorityDiff {
6768

6869
implicit def diffForIterable[T, C[W] <: Iterable[W]](implicit
6970
dt: Diff[T],
70-
matcher: ObjectMatcher[(Int, T)]
71+
matcher: ObjectMatcher[IterableEntry[T]]
7172
): Diff[C[T]] = new DiffForIterable[T, C](dt, matcher)
7273
}
7374

@@ -113,12 +114,12 @@ case class DerivedDiffLens[T, U](outer: Diff[T], path: List[String]) {
113114
}
114115
def ignore(): Derived[Diff[T]] = Derived(outer.modifyUnsafe(path: _*)(Diff.ignored))
115116

116-
def withMapMatcher[K, V](m: ObjectMatcher[(K, V)])(implicit
117+
def withMapMatcher[K, V](m: ObjectMatcher[MapEntry[K, V]])(implicit
117118
ev1: U <:< scala.collection.Map[K, V]
118119
): Derived[Diff[T]] =
119120
Derived(outer.modifyMatcherUnsafe(path: _*)(m))
120121
def withSetMatcher[V](m: ObjectMatcher[V])(implicit ev2: U <:< scala.collection.Set[V]): Derived[Diff[T]] =
121122
Derived(outer.modifyMatcherUnsafe(path: _*)(m))
122-
def withListMatcher[V](m: ObjectMatcher[(Int, V)])(implicit ev3: U <:< Iterable[V]): Derived[Diff[T]] =
123+
def withListMatcher[V](m: ObjectMatcher[IterableEntry[V]])(implicit ev3: U <:< Iterable[V]): Derived[Diff[T]] =
123124
Derived(outer.modifyMatcherUnsafe(path: _*)(m))
124125
}

core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ object ObjectMatcher extends LowPriorityObjectMatcher {
1515
ObjectMatcher[U].isSameObject(f(left), f(right))
1616

1717
/** Given key-value (K,V) pairs match them using V's objectMatcher */
18-
def byValue[K, V: ObjectMatcher]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), V](_._2)
18+
def byValue[K, V: ObjectMatcher]: ObjectMatcher[MapEntry[K, V]] = ObjectMatcher.by[MapEntry[K, V], V](_.value)
1919

2020
/** Given key-value (K,V) pairs, where V is a type of product and U is a property of V, match them using U's objectMatcher */
21-
def byValue[K, V, U: ObjectMatcher](f: V => U): ObjectMatcher[(K, V)] =
21+
def byValue[K, V, U: ObjectMatcher](f: V => U): ObjectMatcher[MapEntry[K, V]] =
2222
ObjectMatcher.byValue[K, V](ObjectMatcher.by[V, U](f))
2323

2424
implicit def optionMatcher[T: ObjectMatcher]: ObjectMatcher[Option[T]] = (left: Option[T], right: Option[T]) => {
@@ -29,7 +29,10 @@ object ObjectMatcher extends LowPriorityObjectMatcher {
2929
}
3030

3131
/** Given key-value (K,V) pairs, match them using K's objectMatcher */
32-
implicit def byKey[K: ObjectMatcher, V]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), K](_._1)
32+
implicit def byKey[K: ObjectMatcher, V]: ObjectMatcher[MapEntry[K, V]] = ObjectMatcher.by[MapEntry[K, V], K](_.key)
33+
34+
type IterableEntry[T] = MapEntry[Int, T]
35+
case class MapEntry[K, V](key: K, value: V)
3336
}
3437

3538
trait LowPriorityObjectMatcher {

core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
package com.softwaremill.diffx.instances
22

33
import com.softwaremill.diffx.Matching.{MatchingResults, matching}
4+
import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry}
45
import com.softwaremill.diffx._
56

67
import scala.collection.immutable.{ListMap, ListSet}
78

89
private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](
910
dt: Diff[T],
10-
matcher: ObjectMatcher[(Int, T)]
11+
matcher: ObjectMatcher[IterableEntry[T]]
1112
) extends Diff[C[T]] {
1213
override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) {
1314
(left, right) =>
1415
val keys = Range(0, Math.max(left.size, right.size))
1516

1617
val leftAsMap = left.toList.lift
1718
val rightAsMap = right.toList.lift
18-
val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => k -> v }
19-
val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => k -> v }
19+
val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => MapEntry(k, v) }
20+
val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => MapEntry(k, v) }
2021

21-
val adjustedMatcher = context.getMatcherOverride[(Int, T)].getOrElse(matcher)
22+
val adjustedMatcher = context.getMatcherOverride[IterableEntry[T]].getOrElse(matcher)
2223
val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) =
2324
matching(leftv2, rightv2, adjustedMatcher)
2425
val leftDiffs = unMatchedLeftInstances
2526
.diff(unMatchedRightInstances)
26-
.collectFirst { case (k, v) => k -> DiffResultAdditional(v) }
27+
.collectFirst { case MapEntry(k, v) => k -> DiffResultAdditional(v) }
2728
.toList
2829
val rightDiffs = unMatchedRightInstances
2930
.diff(unMatchedLeftInstances)
30-
.collectFirst { case (k, v) => k -> DiffResultMissing(v) }
31+
.collectFirst { case MapEntry(k, v) => k -> DiffResultMissing(v) }
3132
.toList
32-
val matchedDiffs = matchedInstances.map { case (l, r) => l._1 -> dt(l._2, r._2, context) }.toList
33+
val matchedDiffs = matchedInstances.map { case (l, r) => l.key -> dt(l.value, r.value, context) }.toList
3334

3435
val diffs = ListMap((matchedDiffs ++ leftDiffs ++ rightDiffs).map { case (k, v) => k.toString -> v }: _*)
3536
DiffResultObject("List", diffs)

core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package com.softwaremill.diffx.instances
22

33
import com.softwaremill.diffx.Matching._
4+
import com.softwaremill.diffx.ObjectMatcher.MapEntry
45
import com.softwaremill.diffx._
56

67
private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](
7-
matcher: ObjectMatcher[(K, V)],
8+
matcher: ObjectMatcher[MapEntry[K, V]],
89
diffKey: Diff[K],
910
diffValue: Diff[V]
1011
) extends Diff[C[K, V]] {
@@ -13,19 +14,27 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]]
1314
right: C[K, V],
1415
context: DiffContext
1516
): DiffResult = nullGuard(left, right) { (left, right) =>
16-
val adjustedMatcher = context.getMatcherOverride[(K, V)].getOrElse(matcher)
17+
val adjustedMatcher = context.getMatcherOverride[MapEntry[K, V]].getOrElse(matcher)
1718
val MatchingResults(unMatchedLeftKeys, unMatchedRightKeys, matchedKeys) =
18-
matching(left.toSet, right.toSet, adjustedMatcher, diffKey.contramap[(K, V)](_._1), context)
19+
matching(
20+
left.map { case (k, v) => MapEntry.apply(k, v) }.toSet,
21+
right.map { case (k, v) => MapEntry.apply(k, v) }.toSet,
22+
adjustedMatcher,
23+
diffKey.contramap[MapEntry[K, V]](_.key),
24+
context
25+
)
1926

2027
val leftDiffs = unMatchedLeftKeys
2128
.diff(unMatchedRightKeys)
22-
.collectFirst { case (k, v) => DiffResultAdditional(k) -> DiffResultAdditional(v) }
29+
.collectFirst { case MapEntry(k, v) => DiffResultAdditional(k) -> DiffResultAdditional(v) }
2330
.toList
2431
val rightDiffs = unMatchedRightKeys
2532
.diff(unMatchedLeftKeys)
26-
.collectFirst { case (k, v) => DiffResultMissing(k) -> DiffResultMissing(v) }
33+
.collectFirst { case MapEntry(k, v) => DiffResultMissing(k) -> DiffResultMissing(v) }
2734
.toList
28-
val matchedDiffs = matchedKeys.map { case (l, r) => diffKey(l._1, r._1) -> diffValue(l._2, r._2, context) }.toList
35+
val matchedDiffs = matchedKeys.map { case (l, r) =>
36+
diffKey(l.key, r.key) -> diffValue(l.value, r.value, context)
37+
}.toList
2938

3039
val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs
3140
DiffResultMap(diffs.toMap)

core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.softwaremill.diffx.test
22

3+
import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry}
34
import com.softwaremill.diffx.generic.auto._
45
import com.softwaremill.diffx._
56
import org.scalatest.freespec.AnyFreeSpec
@@ -373,7 +374,15 @@ class DiffTest extends AnyFreeSpec with Matchers {
373374
"compare lists using object matcher comparator" in {
374375
val o1 = Organization(List(p1, p2))
375376
val o2 = Organization(List(p2, p1))
376-
implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.name)
377+
implicit val om: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(_.name)
378+
compare(o1, o2).isIdentical shouldBe true
379+
}
380+
381+
"compare lists using object matcher comparator when matching by pair" in {
382+
val p2WithSameNameAsP1 = p2.copy(name = p1.name)
383+
val o1 = Organization(List(p1, p2WithSameNameAsP1))
384+
val o2 = Organization(List(p2WithSameNameAsP1, p1))
385+
implicit val om: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(p => (p.name, p.age))
377386
compare(o1, o2).isIdentical shouldBe true
378387
}
379388

@@ -652,7 +661,7 @@ class DiffTest extends AnyFreeSpec with Matchers {
652661
}
653662

654663
"match map entries by values" in {
655-
implicit val om: ObjectMatcher[(KeyModel, String)] = ObjectMatcher.byValue
664+
implicit val om: ObjectMatcher[MapEntry[KeyModel, String]] = ObjectMatcher.byValue
656665
val uuid1 = UUID.randomUUID()
657666
val uuid2 = UUID.randomUUID()
658667
val a1 = MyLookup(Map(KeyModel(uuid1, "k1") -> "val1"))

docs-sources/usage/sequences.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ In below example we tell `diffx` to compare these maps by paring entries by valu
3434
```scala mdoc:reset:silent
3535
import com.softwaremill.diffx._
3636
import com.softwaremill.diffx.generic.auto._
37+
import com.softwaremill.diffx.ObjectMatcher.MapEntry
3738
case class Person(id: String, name: String)
3839

3940
val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id)
40-
implicit val om: ObjectMatcher[(String, Person)] = ObjectMatcher.byValue(personMatcher)
41+
implicit val om: ObjectMatcher[MapEntry[String, Person]] = ObjectMatcher.byValue(personMatcher)
4142
val bob = Person("1","Bob")
4243
```
4344

@@ -47,14 +48,15 @@ compare(Map("1" -> bob), Map("2" -> bob))
4748

4849
Last but not least you can use `objectMatcher` to customize paring when comparing indexed collections.
4950
Such collections are treated similarly to maps (they use key-value object matcher),
50-
but the key type is bound to `Int`.
51+
but the key type is bound to `Int` (`IterableEntry` is an alias for `MapEntry[Int,V]`).
5152

5253
```scala mdoc:reset:silent
5354
import com.softwaremill.diffx._
5455
import com.softwaremill.diffx.generic.auto._
56+
import com.softwaremill.diffx.ObjectMatcher.IterableEntry
5557
case class Person(id: String, name: String)
5658

57-
implicit val personMatcher: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.id)
59+
implicit val personMatcher: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(_.id)
5860
val bob = Person("1","Bob")
5961
val alice = Person("2","Alice")
6062
```

0 commit comments

Comments
 (0)