softwaremill / diffx

Pretty diffs for scala case classes
Apache License 2.0
342 stars 30 forks source link

Compare collection by single field using matchByValue #430

Closed yadavan88 closed 1 year ago

yadavan88 commented 1 year ago

Hi, probably I misunderstood, but I have a confusion about the collection comparison.

Is method matchByValue for comparing the collections only by that particular field? For example, i am using the same example from the website

  case class Person(age: Int, weight: Int)
  case class Organization(peopleList: List[Person])
  val org1 = Organization(List(Person(10,60), Person(20, 65)))
  val org2 = Organization(List(Person(15,60), Person(25, 65)))

  implicit val diffOrg: Diff[Organization] = Diff.summon[Organization]
    .modify(_.peopleList).matchByValue(_.weight)

  println(compare(org1,org2).show())

Here I thought that the diff check will only look for the field weight. In the sample code, age is different but weight is same. But I am getting the isIdentical as false and the difference is shown for age.

Is this the intended behavior?

Here is the screenshot of above code:

image
ghostbuster91 commented 1 year ago

Hi,

If you want to compare weights instead of whole Person classes you can use ignore modifier to exclude some fields from being compared.

In the mentioned case that would look as follows:

  case class Person(age: Int, weight: Int)
  case class Organization(peopleList: List[Person])
  val org1 = Organization(List(Person(10,60), Person(20, 65)))
  val org2 = Organization(List(Person(15,60), Person(25, 65)))

  implicit val diffPerson: Diff[Person] = Diff.summon[Person].ignore(_.age)

  println(compare(org1,org2).show())

which will output something like:

Organization(
  peopleList: List(
    0: Person(
       age: <ignored>,
       weight: 60
    ),
    1: Person(
       age: <ignored>,
       weight: 65
    ),
  )
)

and for that result isIdentical returns true.

The matchByValue modifier is used to pair objects for comparison within a container like List by a given predicate instead of matching them by their position in the container.

I hope that helps.

yadavan88 commented 1 year ago

Hi @ghostbuster91 Thank you for your response. I actually implemented at first using the ignored method. But I got confused when I read the sequence part of the documentation. Thought that we can use matchByValue for comparison without creating Diff instance for the type within collection.

.modify(_.peopleList).matchByValue(_.weight) -> I mis-understood as something like sortBy operation.

Thanks for the clarification :)

ghostbuster91 commented 1 year ago

If you don't want to create a diff instance explicitly for the inner type you can always do:

    implicit val orgDiff: Diff[Organization] = Diff
      .summon[Organization]
      .ignore(_.people.each.age)

Btw if you have an idea how to improve the documentation and make it clearer don't hesitate to submit a PR! :)

yadavan88 commented 1 year ago

Thanks @ghostbuster91 This looks nice :) I will try to create a PR in the next few days with some doc change of what I understood.

yadavan88 commented 1 year ago

@ghostbuster91 I will try to add some additional sentences to the doc soon. I had recently used diffx for the first time and was really impressed by how easy it is to use. I also wrote a blog article regarding it here and appreciate if you could have a look at it sometime when you have time. I hope I haven't made some blunders in the article :)

ghostbuster91 commented 1 year ago

I am glad to hear that you liked it :)

I read the article, it is very good :+1: , I have only one small nitpick:

We can summon the instance of Diff[Transaction] and invoke the ignore method with the necessary field. In Scala-2, instead of summon, we can use implicitly[Diff[Transaction]] and invoke the ignore method.

The summon method should also work for scala-2. Did you encounter any problems with it?

There is also one more thing, for any future work I would recommend you to use: https://github.com/jatcwang/difflicious (at the moment it doesn't have auto-derivation but we are working on it)

It is a better rewrite of diffx. I will try to port there any missing features from diffx.

yadavan88 commented 1 year ago

Hi @ghostbuster91 Thanks for the appreciation :) I didn't have any problem as such. I used implicitly in my Scala 2 code directly without thinking much (just assumed that summon works only in Scala 3 without giving much thought). I will try using summon as well :)

Will have a look at the diffilicious library as well. Just a small doubt, so this diff library will no longer be maintained in the near future ?

yadavan88 commented 1 year ago

Could I also ask one more silly question here ? Sorry to spam you with this. Is it possible to ignore all fields of a particular type without adding ignore for each field? For example, I have a very nested structure with some transactional data. There are more than 5 DateTime fields, which I want to ignore no matter in which case class it appears. For now, I added ignore for each nested fields, but was wondering if it is possible (either in diffx or sometime in future in diffilicious)?

ghostbuster91 commented 1 year ago

Will have a look at the diffilicious library as well. Just a small doubt, so this diff library will no longer be maintained in the near future ?

I plan to maintain it as long as it will be needed, however I would like to start migrating users to difflicious as soon as we reach the feature parity. Then, I will create a scalafix rule to ease the migration as much as possible. It is hard to tell when it will happen, so for now you can consider diffx to be maintained for foreseeable future.

Is it possible to ignore all fields of a particular type without adding ignore for each field?

If I understand you correctly you can just create a Diff instance for that particular type that will be always yield ignored result.