9

my aim is to copy fields of one object into another, but only those that aren't null. I don't want to assign it explicitly. A more generic solution would be very useful and easier to maintain i.e. for implementing PATCH in REST API where you allow providing only specific fields.

I saw this similar thread and I'm trying to implement some of the ideas from here: Helper in order to copy non null properties from object to another ? (Java)

But the objects aren't altered in any way after the program execution.

So here are my example classes created for example:

class Person {
    String name;
    int age;
    Pet friend;

    public Person() {
    }

    public Person(String name, int age, Pet friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    // getters and setters here
}

class Pet {
    String name;
    int age;

    public Pet(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getters and setters here
}

Here is my overridden copyProperty method:

import org.apache.commons.beanutils.BeanUtilsBean;
import java.lang.reflect.InvocationTargetException;

public class MyBeansUtil extends BeanUtilsBean {

@Override
public void copyProperty(Object dest, String name, Object value)
        throws IllegalAccessException, InvocationTargetException {
    if(value == null) return;
    super.copyProperty(dest, name, value);
}
}

... and here is the place I'm trying to test it on some examples:

public class SandBox {
    public static void main(String[] args) {
        Person db = new Person("John", 36, new Pet("Lucy", 3));
        Person db2 = new Person("John", 36, new Pet("Lucy", 2));
        Person db3 = new Person("John", 36, new Pet("Lucy", 4));

        Person in = new Person();
        in.age = 17;
        in.name = "Paul";
        in.friend = new Pet(null, 35);

        Person in2 = new Person();
        in2.name = "Damian";

        Person in3 = new Person();
        in3.friend = new Pet("Lup", 25);

        try {
            BeanUtilsBean notNull  =new MyBeansUtil();
            notNull.copyProperties(db, in);
            notNull.copyProperties(db2, in2);
            notNull.copyProperties(db3, in3);

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

Unfortunately, the original objects db, db1, db2 stay the same as they were. Am I doing something wrong here?

kiedysktos
  • 3,910
  • 7
  • 31
  • 40
  • 1
    Change Access modifier of class declaration to public to make it work. public class Person { } would fix this issue – Nagaraddi Dec 13 '16 at 16:21
  • thanks for the answer - I already came to a solution that is better for me, because I avoid dependency on apache.commons.beanutilsbean. Now I know both ways :) – kiedysktos Dec 14 '16 at 11:45
  • @kiedysktos I have a similar use-case, and would love to know what other solution worked for you. If you can elaborate that, it will be great :) – hardcoder Jan 04 '18 at 13:02
  • unfortunately the accepted answer represents only way I tried – kiedysktos Jan 19 '18 at 11:32

5 Answers5

9

I ended up using Spring BeanUtils library. Here is my working method:

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.lang.reflect.Field;
import java.util.Collection;

public class MyBeansUtil<T> {
    public T copyNonNullProperties(T target, T in) {
        if (in == null || target == null || target.getClass() != in.getClass()) return null;

        final BeanWrapper src = new BeanWrapperImpl(in);
        final BeanWrapper trg = new BeanWrapperImpl(target);

        for (final Field property : target.getClass().getDeclaredFields()) {
            Object providedObject = src.getPropertyValue(property.getName());
            if (providedObject != null && !(providedObject instanceof Collection<?>)) {
                trg.setPropertyValue(
                        property.getName(),
                        providedObject);
            }
        }
        return target;
    }
}

It works fine, but notice that it ignores fields that are collections. That's on purpose, I handle them separately.

kiedysktos
  • 3,910
  • 7
  • 31
  • 40
  • 2
    BeanWrapperImpl is documented as internal only so it should not be used. I think the answer provided here based on commons-beanutils is better : https://stackoverflow.com/questions/1301697/helper-in-order-to-copy-non-null-properties-from-object-to-another-java – loicmathieu Feb 18 '19 at 10:50
  • yup, this link is in my question, but it didn't work for me - that's why I implemented it in this way – kiedysktos Feb 27 '19 at 10:20
  • to @loicmathieu's point, instead of new BeanWrapperImpl(target) use PropertyAccessorFactory.forBeanPropertyAccess(target) – mjj1409 Sep 05 '20 at 13:51
7

You can create your own method to copy properties while ignoring null values.

public static String[] getNullPropertyNames (Object source) {
    final BeanWrapper src = new BeanWrapperImpl(source);
    java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

    Set<String> emptyNames = new HashSet<String>();
    for(java.beans.PropertyDescriptor pd : pds) {
        Object srcValue = src.getPropertyValue(pd.getName());
        if (srcValue == null) emptyNames.add(pd.getName());
    }
    String[] result = new String[emptyNames.size()];
    return emptyNames.toArray(result);
}

// then use Spring BeanUtils to copy and ignore null
public static void myCopyProperties(Object src, Object target) {
    BeanUtils.copyProperties(src, target, getNullPropertyNames(src))
}
Sudhakar
  • 3,104
  • 2
  • 27
  • 36
6

Using BeanUtils and java8 we can achieve this:

BeanUtils.copyProperties(Object_source, Object_target, getNullPropertyNames(Object_source));

private String[] getNullPropertyNames(Object source) {
        final BeanWrapper wrappedSource = new BeanWrapperImpl(source);
        return Stream.of(wrappedSource.getPropertyDescriptors()).map(FeatureDescriptor::getName)
                .filter(propertyName -> wrappedSource.getPropertyValue(propertyName) == null).toArray(String[]::new);
    }
2

Using ProprtyUtils, we can achieve this using:

    private void copyNonNullProperties(Object destination,
            Object source) {
        try {
            PropertyUtils.describe(source).entrySet().stream()
                    .filter(source -> source.getValue() != null)
                    .filter(source -> !source.getKey().equals("class"))
                    .forEach(source -> {
                        try {
                            PropertyUtils.setProperty(destination, source.getKey(), source.getValue());
                        } catch (Exception e22) {
                            log.error("Error setting properties : {}", e22.getMessage());
                        }
                    });

        } catch (Exception e1) {
            log.error("Error setting properties : {}", e1.getMessage());
        }

    }
KayV
  • 12,987
  • 11
  • 98
  • 148
2

I have faced a similar problem recently. I was asked to implement a generic solution for implementing PATCH in REST API where you allow providing only specific field.

The project is a Java one with MongoDB.

In the beginning I thought would be possible to solve using the Mongo java driver and the operation $set passing the document with only the fields that should be modified. After extensive researching I realized that it doesn't work this way. If you have nested classes it won't update selectively the inner class but instead replace it. I have tried several options using directly the Mongo java driver and SpringMongoDB java API.

Then I went to the BeanUtils solution as described by the author @kiedysktos.

    public class MyBeansUtil extends BeanUtilsBean {

    @Override
    public void copyProperty(Object dest, String name, Object value)
        throws IllegalAccessException, InvocationTargetException {
        if(value == null) return;
        super.copyProperty(dest, name, value);
    }
    }

It turns out that doing only this it won't work properly as well. Imagine you call your PATCH in the following way

{ "name": "John Doe", "friend": { "age":2 } }

The intent of this call is to update the age of the single pet of John Doe to 2. However the overridden code above will replace the entire Pet structure to

{ "name": null, "age" : 2
} erasing the name information.

My final solutions was to call recursively where I found a nested inner class. This way each one will be copied maintaining the previous information. To do that each class involved should implement a marking interface.

    Person implements NonNullCopy
    Pet implements NonNullCopy

Finally, the code:

class NullAwareBeanUtils extends BeanUtilsBean {
    
    
    @Override
    public void copyProperty(Object dest, String name, Object value)
            throws IllegalAccessException, InvocationTargetException {
        if (value == null)
            return;
        else if(value instanceof NonNullCopy) {
            Class<?> destClazz = value.getClass();
                Class<?> origClazz = dest.getClass();
                String className = destClazz.getSimpleName();
        
                //Recursively invokes copyProperties
                for(Method m : origClazz.getDeclaredMethods()) {
                    if(m.getReturnType().equals(destClazz)) {
                        copyProperties(m.invoke(dest, Collections.EMPTY_LIST.toArray()),value);
                    }                       
                }
                return;
        }

        super.copyProperty(dest, name, value);
    }

       
}


Notice that this solution is generic if the class implements the marking interface.