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
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;
}
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);
}
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;
}
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);
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.