Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge two complex objects in java

Tags:

java

merge

I have two java objects and I want to merge them into single object. Problem is the two objects does not contain plain primitive type properties(fields) they contain complex type properties(like object of other type and list of objects of other type).

Object 1: Returns by setting up some properties (fields) and

Objects 2: returns by setting up some properties (fields) or it may return new objects of the type which it holds but not returned by object 1.

Both object 1 and object 2 are of same type.

Result Object 3 = obj1 properties + update the properties from obj 2 if same type as obj1 + new updated object from obj2

like image 467
user3860615 Avatar asked Jul 21 '14 13:07

user3860615


2 Answers

Its pretty easy to do using the org.springframework.beans.BeanUtils class provided by spring. Or the Apache Commons BeanUtils library which I believe Springs version is either based on or is the same as.

public static <T> T combine2Objects(T a, T b) throws InstantiationException, IllegalAccessException {
    // would require a noargs constructor for the class, maybe you have a different way to create the result.
    T result = (T) a.getClass().newInstance();
    BeanUtils.copyProperties(a, result);
    BeanUtils.copyProperties(b, result);
    return result;
}

if you cant or dont have a noargs constructor maybe you just pass in the result

public static <T> T combine2Objects(T a, T b, T destination) {
    BeanUtils.copyProperties(a, destination);
    BeanUtils.copyProperties(b, destination);
    return destination;
}

If you dont want null properties being copied you can use something like this:

public static void nullAwareBeanCopy(Object dest, Object source) throws IllegalAccessException, InvocationTargetException {
    new BeanUtilsBean() {
        @Override
        public void copyProperty(Object dest, String name, Object value)
                throws IllegalAccessException, InvocationTargetException {
            if(value != null) {
                super.copyProperty(dest, name, value);
            }
        }
    }.copyProperties(dest, source);
}

Nested object solution

Heres a bit more robust solution. It supports nested object copying, objects 1+ level deep will no longer be copied by reference, instead Nested objects will be cloned or their properties be copied individually.

/**
 * Copies all properties from sources to destination, does not copy null values and any nested objects will attempted to be
 * either cloned or copied into the existing object. This is recursive. Should not cause any infinite recursion.
 * @param dest object to copy props into (will mutate)
 * @param sources
 * @param <T> dest
 * @return
 * @throws IllegalAccessException
 * @throws InvocationTargetException
 */
public static <T> T copyProperties(T dest, Object... sources) throws IllegalAccessException, InvocationTargetException {
    // to keep from any chance infinite recursion lets limit each object to 1 instance at a time in the stack
    final List<Object> lookingAt = new ArrayList<>();

    BeanUtilsBean recursiveBeanUtils = new BeanUtilsBean() {

        /**
         * Check if the class name is an internal one
         * @param name
         * @return
         */
        private boolean isInternal(String name) {
            return name.startsWith("java.") || name.startsWith("javax.")
                    || name.startsWith("com.sun.") || name.startsWith("javax.")
                    || name.startsWith("oracle.");
        }

        /**
         * Override to ensure that we dont end up in infinite recursion
         * @param dest
         * @param orig
         * @throws IllegalAccessException
         * @throws InvocationTargetException
         */
        @Override
        public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
            try {
                // if we have an object in our list, that means we hit some sort of recursion, stop here.
                if(lookingAt.stream().anyMatch(o->o == dest)) {
                    return; // recursion detected
                }
                lookingAt.add(dest);
                super.copyProperties(dest, orig);
            } finally {
                lookingAt.remove(dest);
            }
        }

        @Override
        public void copyProperty(Object dest, String name, Object value)
                throws IllegalAccessException, InvocationTargetException {
            // dont copy over null values
            if (value != null) {
                // attempt to check if the value is a pojo we can clone using nested calls
                if(!value.getClass().isPrimitive() && !value.getClass().isSynthetic() && !isInternal(value.getClass().getName())) {
                    try {
                        Object prop = super.getPropertyUtils().getProperty(dest, name);
                        // get current value, if its null then clone the value and set that to the value
                        if(prop == null) {
                            super.setProperty(dest, name, super.cloneBean(value));
                        } else {
                            // get the destination value and then recursively call
                            copyProperties(prop, value);
                        }
                    } catch (NoSuchMethodException e) {
                        return;
                    } catch (InstantiationException e) {
                        throw new RuntimeException("Nested property could not be cloned.", e);
                    }
                } else {
                    super.copyProperty(dest, name, value);
                }
            }
        }
    };


    for(Object source : sources) {
        recursiveBeanUtils.copyProperties(dest, source);
    }

    return dest;
}

Its kinda quick and dirty but works well. Since it does use recursion and the potential is there for infinite recursion I did place in a safety against.

like image 149
ug_ Avatar answered Sep 17 '22 18:09

ug_


The below method will ignore the serialVersionUID, iterate through all the fields and copy the non-null values from object a --> object b if they are null in b. In other words, if any field is null in b, take it from a if there its not null.

public static <T> T combine2Objects(T a, T b) throws InstantiationException,IllegalAccessException{
            T result = (T) a.getClass().newInstance();
            Object[] fields = Arrays.stream(a.getClass().getDeclaredFields()).filter(f -> !f.getName().equals("serialVersionUID")).collect(Collectors.toList()).toArray();
            for (Object fieldobj : fields) {
                Field field = (Field) fieldobj;
                field.set(result, field.get(b) != null ? field.get(b) : field.get(a));
            }
            return result;
    }
like image 37
NullPointer Avatar answered Sep 20 '22 18:09

NullPointer