Как сравнить два объекта и получить измененные поля

#java #object-comparison

#java #объект-сравнение

Вопрос:

Здесь я регистрирую изменения, которые были внесены в конкретную запись объекта. Итак, я сравниваю старую запись и обновленную запись, чтобы записать обновленные поля в виде строки. Есть идеи, как я могу это сделать?

Комментарии:

1. Сравнивать поле за полем и регистрировать измененные?

2. Кажется, вы недовольны сравнением каждого атрибута. В этом случае JAVERS может быть вариантом.

3. Ваша старая запись хранится в объекте, и вы устанавливаете для нее новую запись, или вы используете объект 2 record для хранения этих двух записей?

Ответ №1:

Ну, я нашел решение, как показано ниже :

   private static List<String> getDifference(Object s1, Object s2) throws IllegalAccessException {
    List<String> values = new ArrayList<>();
    for (Field field : s1.getClass().getDeclaredFields()) {
        field.setAccessible(true);
        Object value1 = field.get(s1);
        Object value2 = field.get(s2);
        if (value1 != null amp;amp; value2 != null) {
            if (!Objects.equals(value1, value2)) {
                values.add(String.valueOf(field.getName() ": " value1 " -> " value2));
            }
        }
    }
    return values;
}
  

Ответ №2:

Для этого вы можете использовать библиотеку javers.

   <groupId>org.javers</groupId>
  <artifactId>javers-core</artifactId>
  

POJO:

     public class Person {
    private Integer id;
    private String name;

    // standard getters/constructors
}
  

Использование:

     @Test
public void givenPersonObject_whenApplyModificationOnIt_thenShouldDetectChange() {
    // given
    Javers javers = JaversBuilder.javers().build();

    Person person = new Person(1, "Michael Program");
    Person personAfterModification = new Person(1, "Michael Java");

    // when
    Diff diff = javers.compare(person, personAfterModification);

    // then
    ValueChange change = diff.getChangesByType(ValueChange.class).get(0);

    assertThat(diff.getChanges()).hasSize(1);
    assertThat(change.getPropertyName()).isEqualTo("name");
    assertThat(change.getLeft()).isEqualTo("Michael Program");
    assertThat(change.getRight()).isEqualTo("Michael Java");
}
  

Кроме того, поддерживаются и другие варианты использования.

Комментарии:

1. Это не отвечает на вопрос. javers показывает только разницу, но не предоставляет информацию о поле.

Ответ №3:

  1. возможно, этот метод поможет вам решить вашу проблему
      /**
     * get o1 and o2 different value of field name
     * @param o1 source
     * @param o2 target
     * @return
     * @throws IllegalAccessException
     */
    public static List<String> getDiffName(Object o1,Object o2) throws IllegalAccessException {
        //require o1 and o2 is not null
        if (o1==nullamp;amp;o2==null){
            return Collections.emptyList();
        }
        //if only one has null
        if (o1 == null){
            return getAllFiledName(o2);
        }
        if (o2 == null){
            return getAllFiledName(o1);
        }
        //source field
        Field[] fields=o1.getClass().getDeclaredFields();
        List<String> fieldList=new ArrayList<>(fields.length);
        //if class is same using this to call
        if (o1.getClass().equals(o2.getClass())){
            //loop field to equals the field
            for (Field field : fields) {
                //to set the field access
                field.setAccessible(true);
                Object source = field.get(o1);
                Object target = field.get(o2);
                //using jdk8 equals to compare two objects
                if (!Objects.equals(source, target)){
                    fieldList.add(field.getName());
                }
            }
        }else {
            //maybe o1 class is not same as o2 class
            Field[] targetFields=o2.getClass().getDeclaredFields();
            List<String> sameFieldNameList=new ArrayList<>();
            //loop o1 field
            for (Field field : fields) {
                String name = field.getName();
                //loop target field to get same field
                for (Field targetField : targetFields) {
                    //if name is equal to compare
                    if (targetField.getName().equals(name)){
                        //add same field to list
                        sameFieldNameList.add(name);
                        //set access
                        field.setAccessible(true);
                        Object source = field.get(o1);
                        //set target access
                        targetField.setAccessible(true);
                        Object target = targetField.get(o2);
                        //equals
                        if (!Objects.equals(source, target)){
                            fieldList.add(field.getName());
                        }
                    }
                }
            }
            //after loop add different source
            for (Field targetField : targetFields) {
                //add not same field
                if (!sameFieldNameList.contains(targetField.getName())){
                    fieldList.add(targetField.getName());
                }
            }
        }
        return fieldList;
    }

    /**
     * getAllFiledName
     * @param obj
     * @return
     */
    private static List<String> getAllFiledName(Object obj) {
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        List<String> list=new ArrayList<>(declaredFields.length);
        for (Field field : declaredFields) {
            list.add(field.getName());
        }
        return list;
    }
  
  1. этот метод может сравнивать два объекта, которые имеют одинаковое поле имени, если у них разные поля, возвращает все имена полей

Ответ №4:

Универсальная функция Kotlin с отражением

Это не идеальный ответ на этот вопрос, но, возможно, кто-то мог бы им воспользоваться.

 data class Difference(val old: Any?, val new: Any?)

fun findDifferencesInObjects(
    old: Any,
    new: Any,
    propertyPath: String? = null
): MutableMap<String, Difference> {
    val differences = mutableMapOf<String, Difference>()
    if (old::class != new::class) return differences

    @Suppress("UNCHECKED_CAST")
    old::class.memberProperties.map { property -> property as KProperty1<Any, *>
        val newPropertyPath = propertyPath?.plus('.')?.plus(property.name) ?: property.name
        property.isAccessible = true
        val oldValue = property.get(old)
        val newValue = property.get(new)

        if (oldValue == null amp;amp; newValue == null) return@map
        if (oldValue == null || newValue == null) {
            differences[newPropertyPath] = Difference(oldValue, newValue)
            return@map
        }

        if (oldValue::class.isData || newValue::class.isData) {
            differences.putAll(findDifferencesInObject(oldValue, newValue, newPropertyPath))
        } else if (!Objects.equals(oldValue, newValue)) {
            differences[newPropertyPath] = Difference(oldValue, newValue)
        }
    }
    return differences
}
  

Результат

 {
  "some.nested.and.changed.field": {
    "old": "this value is old",
    "new": "this value is new"
  },
  ...
}