equatable icon indicating copy to clipboard operation
equatable copied to clipboard

Feature Request!!! Add Support for object diffs

Open ThinkDigitalSoftware opened this issue 4 years ago • 20 comments

Is your feature request related to a problem? Please describe. I'm always frustrated when debugging and I think an object should be equal to another and it's not.

Describe the solution you'd like I'd like to say

if(obj1 != obj2){
  Map diff = obj2.difference(object1); // was going to go with operator -, 
             //but that can be overrriden and cause problems.
}



**Describe alternatives you've considered**
Writing one per class. But that's tedious.


ThinkDigitalSoftware avatar Jul 05 '19 20:07 ThinkDigitalSoftware

Hey @ThinkDigitalSoftware 👋 Thanks for the feature request!

Can you provide an example of what the diff would look like? Thanks!

felangel avatar Jul 05 '19 21:07 felangel

Hey. I would guess that it would be something like... of course wildly open for suggestions.

{
  paramName: Diff(presentIn: obj1, value: value)
}
class Diff<T>{
  T presentIn;
  dynamic value;
}

ThinkDigitalSoftware avatar Jul 06 '19 03:07 ThinkDigitalSoftware

@ThinkDigitalSoftware What type of obj1 and obj2? If you want to compare between collection objects as List, Map,..etc, you can use the following way:

Assume obj1 and obj2 are Map.

const DeepCollectionEquality _equality = DeepCollectionEquality();
_equality.equals(obj1, obj2);

phamnhuvu-dev avatar Jul 07 '19 12:07 phamnhuvu-dev

@phamnhuvu-dev it would be a custom class that extends equatable. From what I know, your methods return a boolean value. This is going to have a Set.difference-like functionality

ThinkDigitalSoftware avatar Jul 08 '19 18:07 ThinkDigitalSoftware

This feature can be misleading. Equatable can only differentiate between objects based on the props you pass to it (+ staticly available info). Therefore, the diff can only state what was the prop based on which Equatable would consider 2 objects unequal.

Since props getter is now public, I can image this to be a separate "utility function" that ships with provider but I would not make it part of the core class/functionality. Use case seems very specialised. I don't know where I'd use it in the projects I'm developing other than for debugging/logging (hence the utility nature).

daniel-v avatar Oct 03 '19 09:10 daniel-v

Yes, definitely for debugging. And yes, since it's a getter now, it wont be as easy

ThinkDigitalSoftware avatar Oct 03 '19 15:10 ThinkDigitalSoftware

in flutter_bloc, we have Transition that shows you the currentState and nextState which base on Equatable, so I need a way to compare the diff between curentState and nextState, could someone help me with this? or at least can we do something like convert Object to JSON string by default without writing Map<String, dynamic> toJson() => { 'name': name, 'email': email, };

so if later we want to change the default format we could override it

thanks

hunght avatar Dec 10 '19 07:12 hunght

Well you can write specifically, if (previousState.name!= nextState.name) ....

ThinkDigitalSoftware avatar Dec 10 '19 15:12 ThinkDigitalSoftware

@ThinkDigitalSoftware , your example is too simple, if we have nested object it will too hard to check

hunght avatar Dec 11 '19 07:12 hunght

@ThinkDigitalSoftware , your example is too simple, if we have nested object it will too hard to check

Unless it's not really. You're only comparing objects

ThinkDigitalSoftware avatar Dec 11 '19 07:12 ThinkDigitalSoftware

like we have state contain Post object which has array of comments, so you need to compare all the comments objects right?. so I want to see the diff in this case

hunght avatar Dec 11 '19 08:12 hunght

@hunght can you please provide a sample of what you would like the diff to look like?

felangel avatar Dec 11 '19 13:12 felangel

Since equatable compares objects anyway, then, in the for loop (or however it's implemented) when you're comparing objects,

 if(obj1 == obj2){
    diffMap[fieldName] = true;
else {
    diffMap[fieldName] = false;
}

this could return, for example a Map<String, bool> {} // not sure on implementation, or a custom class like... EquatableDiff(List<Object> equalValues, List<Object> unequalValues); For the map, field name can be runtimeType, or anything that can identify the object. The second option seems preferable though. This is just beneficial for debugging purposes so we don't have to manually inspect the variables to see what's different.

ThinkDigitalSoftware avatar Dec 11 '19 18:12 ThinkDigitalSoftware

@hunght

like we have state contain Post object which has array of comments, so you need to compare all the comments objects right?. so I want to see the diff in this case

I see what you mean. Say it was done with a custom class, a diff would be

EquatableDiff(
    List<Object> equalValues = [postListVariable, commentListVariable], 
    List<Object> unEqualValues = [userListVariable], 
)

Since it's only checking lists and maps to see if are or are not equal using DeepEquality, then it's a single true or false value for the entire list or map. If you want to go deeper, then we'd be discussing Set differences, which can get complex and would, IMO be beyond the scope of this library and this issue.

ThinkDigitalSoftware avatar Dec 11 '19 18:12 ThinkDigitalSoftware

@felangel @ThinkDigitalSoftware this is what I want to achieve, it is written in js, pls check the answer with sample output https://stackoverflow.com/a/8596559/723160

hunght avatar Dec 12 '19 01:12 hunght

this is something similar to redux devtool that they can detect the diff between the current state and next state, and show the detail of the diffs

hunght avatar Dec 12 '19 02:12 hunght

@hunght it's a lot easier to create arbitrary objects in js. You can just add things to them similar to how we add entries to maps in Dart. So the alternative would be returning a map

ThinkDigitalSoftware avatar Dec 18 '19 00:12 ThinkDigitalSoftware

@ThinkDigitalSoftware yes, return a map is work for me too

hunght avatar Dec 18 '19 00:12 hunght

this is something similar to redux devtool that they can detect the diff between the current state and next state, and show the detail of the diffs

Also when working with bloc, I'd love to see my BlocObserver print the fields that differ from the last state only.

SebastianEngel avatar Apr 21 '21 10:04 SebastianEngel

I also ran into a situation where this would be handy. As a proof of concept, something like this may work. Maybe a custom EquatableValueDifference to describe the difference between the two compared values. Equatable Implementation

mixin EquatableDifference on Equatable {
  Iterable<Map<int, Object?>> difference(Equatable other) sync* {
    for (var i = 0; i < props.length; i++) {
      final thisProp = props[i];
      final otherProp = other.props[i];
      if (otherProp != thisProp) yield {i: otherProp};
    }
  }
}

Example Class

class Credentials extends Equatable with EquatableDifference {
  const Credentials({
    required this.username,
    required this.password,
  });

  final String username;
  final String password;

  @override
  List<Object> get props => [username, password];
}

Test

void main() {
  group('EquatableDifference', () {
    test('empty for identical Equatable objects', () {
      const cred1 = Credentials(username: 'username', password: 'password');
      const cred2 = Credentials(username: 'username', password: 'password');
      expect(cred1.difference(cred2), isEmpty);
    });

    test('not empty for identical Equatable objects', () {
      const cred1 = Credentials(username: 'username', password: 'password');
      const cred2 = Credentials(username: 'username2', password: 'password');
      expect(
        cred1.difference(cred2).first,
        {0: 'username2'},
      );
    });

    test('not empty for identical Equatable objects [1]', () {
      const cred1 = Credentials(username: 'username1', password: 'password1');
      const cred2 = Credentials(username: 'username2', password: 'password2');
      expect(
        cred1.difference(cred2).toList(),
        [
          {0: 'username2'},
          {1: 'password2'},
        ],
      );
    });
  });
}

Luckey-Elijah avatar Jun 01 '22 18:06 Luckey-Elijah