How to merge two complex objects in java

前端 未结 5 1348
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-01 23:23

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

相关标签:
5条回答
  • 2021-01-01 23:40

    try this

    public <T> T objectMerge(T local, T remote, boolean toappend) throws Exception {
        Class<?> clazz = local.getClass();
        Object merged = clazz.newInstance();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            Object localValue = field.get(local);
            Object remoteValue = field.get(remote);
            if (localValue != null) {
                String key = "";
                if (localValue.getClass().getSimpleName().toLowerCase().contains("map")) {
                    key = "map";
                } else if (localValue.getClass().getSimpleName().toLowerCase().contains("set")) {
                    key = "set";
                } else if (localValue.getClass().getSimpleName().toLowerCase().contains("list")) {
                    key = "list";
                } else {
                    key = localValue.getClass().getSimpleName();
                }
                switch (key) {
                    case "Default":
                    case "Detail":
                    case "String":
                    case "Date":
                    case "Integer":
                    case "Float":
                    case "Long":
                    case "Double":
                    case "Object":
                        field.set(merged, (remoteValue != null) ? remoteValue : localValue);
                        break;
                    case "map":
                        if (toappend) {
                            ((Map) localValue).putAll((Map) remoteValue);
                        } else {
                            localValue = (remoteValue != null) ? remoteValue : localValue;
                        }
                        field.set(merged, localValue);
                        break;
                    case "list":
                        if (toappend) {
                            ((List) localValue).addAll((List) remoteValue);
                        } else {
                            localValue = (remoteValue != null) ? remoteValue : localValue;
                        }
                        field.set(merged, localValue);
                        break;
                    case "set":
                        if (toappend) {
                            ((Set) localValue).addAll((Set) remoteValue);
                        } else {
                            localValue = (remoteValue != null) ? remoteValue : localValue;
                        }
                        field.set(merged, localValue);
                        break;
                    default:
                        field.set(merged, this.objectMerge(localValue, remoteValue, toappend));
                        break;
                }
            }
        }
        return (T) merged;
    }
    
    0 讨论(0)
  • 2021-01-01 23:48

    try to use class.getFields

        Field[] fields = YourClass.getFields();
        for (Field field : fields) {
             // get value
             YourObject value = field.get(objectInstance);
             // check the values are different, then update 
             field.set(objetInstance, value);    
        }
    
    0 讨论(0)
  • 2021-01-01 23:54

    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;
        }
    
    0 讨论(0)
  • 2021-01-01 23:57
    import java.util.Objects;
    import java.util.function.Consumer;
    import java.util.function.Supplier;
    
    public final class PropertyMerger {
    
        public static <T> void mergeProperty(
                Supplier<T> sourceGetter,
                Supplier<T> targetGetter,
                Consumer<T> targetSetter
        ) {
            var source = sourceGetter.get();
            var target = targetGetter.get();
    
            if (!Objects.equals(source, target)) {
                targetSetter.accept(source);
            }
        }
    
    }
    

    Somwhere in your code:

    PropertyMerger.mergeProperty(facebookOAuth2User::getId, existingFacebookOAuth2UserDB::getFacebookId, existingFacebookOAuth2UserDB::setFacebookId);
    PropertyMerger.mergeProperty(facebookOAuth2User::getName, existingFacebookOAuth2UserDB::getName, existingFacebookOAuth2UserDB::setName);
    
    0 讨论(0)
  • 2021-01-02 00:05

    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.

    0 讨论(0)
提交回复
热议问题