Krishna Chaitanya
Krishna Chaitanya

Reputation: 2663

how to compare two objects and find the fields/properties changed?

I need to write some generic solution to find out what properties in two objects have changed and return the changed properties (not the value).

class Student {
    public String name;
    public Address address;
    public int age;
}

class Address {
    public String hno;
    public String street;
    public int pin;
}

public class Test {
    public static void main(String... arg) {

        Student s1 = new Student();
        s1.name = "Krishna";
        s1.age = 30;
        s1.address = new Address();
        s1.address.hno = "2-2-22";
        s1.address.street = "somewhere";
        s1.address.pin = 123;

        Student s2 = new Student();
        s2.name = "Krishna";
        s2.age = 20;
        s2.address = new Address();
        s2.address.hno = "1-1-11";
        s2.address.street = "nowhere";
        s2.address.pin = 123;

        List<String> = difference(s1, s2);
        // expected result
        // Student.age
        // Student.address.hno
        // Student.address.street
    }
}

Can anyone please suggest some solution?

PS

Overriding equals/hashcode is not an option for me.

I have written the below code but I am not sure how to identify if a type is complex (for example Address)

private static List<String> difference(Student s1, Student s2) throws IllegalArgumentException, IllegalAccessException {

        for(Field field: Student.class.getDeclaredFields()) {
            System.out.println(field.getName() + " " +field.get(s1).equals(field.get(s2)));
        }
        return null;
    }

Upvotes: 14

Views: 32944

Answers (5)

polyackov_ot
polyackov_ot

Reputation: 101

I would like to add Awgtek's method is good but doesn't work with Enum field I added this code before isBaseType(value1.getClass())

if (value1 instanceof Enum && value2 instanceof Enum ) {
    if (!value1.equals(value2) {
        changedProperties.add(parent + "." + field.getName());
        continue;
    }
}

I also advise adding LocalDate.class in BASE_TYPES

And if you want fields from superclass compare too. You need change s1.getClass().getDeclaredFields() -> getAllFields(s1.getClass())

 private static List<Field> getAllFields(Class currentClass) {
    final List<Field> fields = new ArrayList<>(Arrays.asList(currentClass.getDeclaredFields()));

    final Class superclass = currentClass.getSuperclass();
    if (superclass != Object.class) {
        fields.addAll(getAllFields(superclass));
    }

    return fields;
}

Upvotes: 2

awgtek
awgtek

Reputation: 1829

This is an enhancement of Dumbo's difference method, using recursion to check all nested complex fields.

    private static void difference(Object s1, Object s2, List<String> changedProperties, String parent) throws IllegalAccessException {
        for (Field field : s1.getClass().getDeclaredFields()) {
            if (parent == null) {
                parent = s1.getClass().getSimpleName();
            }
            field.setAccessible(true);
            Object value1 = field.get(s1);
            Object value2 = field.get(s2);
            if (value1 == null && value2 == null) {
                continue;
            }
            if (value1 == null || value2 == null) {
                changedProperties.add(parent + "." + field.getName());
            } else {
                if (isBaseType(value1.getClass())) {
                    if (!Objects.equals(value1, value2)) {
                        changedProperties.add(parent + "." + field.getName());
                    }
                } else {
                    difference(value1, value2, changedProperties, parent + "." + field.getName());
                }
            }
        }
    }

    private static final Set<Class> BASE_TYPES = new HashSet(Arrays.asList(
            String.class, Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class));
    public static boolean isBaseType(Class clazz) {
        return BASE_TYPES.contains(clazz);
    }

Sample usage (assumes the referenced model objects have getters/setters):

    public static void main(String[] args) throws IllegalAccessException {
        Student s1 = new Student();
        s1.setName("Krishna");
        s1.setAge(30);
        Address address = new Address();
        s1.setAddress(address);
        s1.getAddress().setHno("2-2-22");
        s1.getAddress().setStreet("somewhere");
        s1.getAddress().setPin(123);

        Student s2 = new Student();
        s2.setName("Krishna");
        s2.setAge(20);
        address = new Address();
        s2.setAddress(address);
        s2.getAddress().setHno("1-1-11");
        s2.getAddress().setStreet("nowhere");
        s2.getAddress().setPin(123);
        List<String> changedProperties = new ArrayList<>();

        difference(s1, s2, changedProperties, null);
        System.out.println("changedProperties = " + changedProperties);
        // expected result
        // Student.age
        // Student.address.hno
        // Student.address.street
    }

Result:

changedProperties = [Student.address.hno, Student.address.street, Student.age]

Primitive adapting checking adapted from https://stackoverflow.com/a/711226/1527469

Upvotes: 12

Dumbo
Dumbo

Reputation: 1827

I think this is the method you searching for:

 private static List<String> difference(Student s1, Student s2) throws IllegalAccessException {
     List<String> changedProperties = new ArrayList<>();
     for (Field field : s1.getClass().getDeclaredFields()) {
        // You might want to set modifier to public first (if it is not public yet)
        field.setAccessible(true);
        Object value1 = field.get(s1);
        Object value2 = field.get(s2); 
        if (value1 != null && value2 != null) {
            System.out.println(field.getName() + "=" + value1);
            System.out.println(field.getName() + "=" + value2);
            if (!Objects.equals(value1, value2)) {
                changedProperties.add(field.getName());
            }
        }
    }
    return changedProperties;
 }

Upvotes: 14

Kartik
Kartik

Reputation: 7907

Although it's an old question, I would like to add an idea:-

  1. Use JAXB to convert java objects to XML.
  2. Use XMLUnit to compare differences in XMLs.

It will give you the names of fields that have changed, the before and after values of those fields, etc.

I admit this is not the best way because it has overhead of marshalling and running XML comparison; and the need to add a couple of libraries. But it might be better than writing field by field comparison (specially deep comparison) and reflection for complex model objects.

Upvotes: 2

azro
azro

Reputation: 54148

1. A solution that match your requirement :

//Add this in Student Class
public static List<String> difference(Student s1, Student s2) {
    List<String> res = new ArrayList<>();
    if (!s1.name.equalsIgnoreCase(s2.name)) res.add("name");
    if (s1.age != s2.age) res.add("age");
    res.addAll(Address.difference(s1.address, s2.address));
    return res;
}

//Add this in Address Class
public static List<String> difference(Address s1, Address s2) {
    List<String> res = new ArrayList<>();
    if (!s1.hno.equalsIgnoreCase(s2.hno)) res.add("adress.hno");
    if (!s1.street.equalsIgnoreCase(s2.street)) res.add("adress.street");
    if (s1.pin != s2.pin) res.add("pin");
    return res;
}

And this will print : [age, adress.hno, adress.street] correct but not easy of more attributs

List<String> list = Student.difference(s1, s2);
System.out.println(list);

2. A version using reflection would result in :

//Add this method in Test Class
private static List<String> difference(Student s1, Student s2) throws IllegalAccessException {
    List<String> res = new ArrayList<>();
    for (Field f : s1.getClass().getFields())
        if (!f.get(s1).equals(f.get(s2)))
            res.add(f.getName());
    return res;
}

And this will print : [address, age] not all correct but no change if more attributs

List<String> list = difference(s1, s2);
System.out.println(list);

3. Tips :

  • create constructor with all the attributs, don't set them after
  • don't set attributs public & use getters and setters
  • override equals method in both
class Student {
    private String name;    private Address address;      private int age;      
    public Student (String n, Address ad, int a){name = n; address = ad; age=a;}
}

class Address {    
    private String hno;     private String street;        private int pin;
    public Address (String a, String s, int p){hno = a; street = s; pin=p;}    
}

//To use in your main of Test (or elsewhere) like this    
Student s1 = new Student("Krishna", new Address("2-2-22", "somewhere", 123), 30);
Student s2 = new Student("Krishna", new Address("1-1-11", "nowhere", 123), 20);           

Upvotes: -1

Related Questions