Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy one object into another changing data types of some fields

Tags:

java

clone

I have two java objects as follows:

class A {
  int a;
  int b;
}

class B {
  int a;
  Double b;
}

A objA = new A();
objA.a = 5;
objA.b = 6;

I want to clone objA into objB such that field b gets converted to Double when accessed from objB i.e.

objB.b = 6.0
objB.a = 5

Note:

  • Classes have to different.
  • Classes are so huge that individual copying and typecasting does not seem like a very good option.
  • I can't extend class B from A because the field names are exactly the same except for few fields which have their data types changed from int to Double in class B.
like image 833
Abhinav Avatar asked Jul 19 '18 13:07

Abhinav


2 Answers

There are frameworks that do a mapping between objects of different classes. Chech out the comments.

If you don't want to use a third-party library, you could write an over-simplified version of what those frameworks offer.

For instance, if the names of the fields are identical, and the difference is only in types, we could write a method(A a, B b, Rules r) which would map a to b by the given rules1:

public static void copyFromAtoB(A a, B b, Map<String, Function<Object, Object>> rules) throws NoSuchFieldException, IllegalAccessException {
    for (Field f : B.class.getDeclaredFields()) {
        final String fName = f.getName();
        final Object aValue = A.class.getDeclaredField(f.getName()).get(a);

        f.set(b, rules.containsKey(fName) ? rules.get(fName).apply(aValue) : aValue);
    }
}

The rules2 tell what function we should apply to a field in order to set the value correctly.

If there is no rule for a field, we assume the types are compatible.

final A a = new A(5, 6);
final B b = new B();

final Map<String, Function<Object, Object>> rules = new HashMap<>();
rules.put("b", i -> Double.valueOf((int)i));  // int -> Double

copyFromAtoB(a, b, rules);

1 Yes, that's a reflection approach - it might be costly and over-engineered, but it seems pretty flexible.
2 Rules are not well-defined because we take an Object and return an Object (Function<Object, Object>).

like image 104
Andrew Tobilko Avatar answered Oct 23 '22 14:10

Andrew Tobilko


If you try to use Apache BeanUtils, as suggested in one of the comments, you'll see that it has a copyProperties method, which can, to some degree, cast your types. For example, you can automatically get your double from an int, as it was in your example. However, if the properties really are not compatible, you'll get an exception and there seems to be no way to say you want to skip it. My approach is to extend the BeanUtilsBean class and add a method similar to copyProperties(), but with an extra argument: a list of excepted properties:

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

import org.apache.commons.beanutils.BeanUtilsBean;

public class MyBeanUtilsBean extends BeanUtilsBean {

    public void copyPropertiesExcept(Object dest, Object orig, String... exceptProperties)
            throws IllegalAccessException, InvocationTargetException {
        PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
        for (int i = 0; i < origDescriptors.length; i++) {
            String name = origDescriptors[i].getName();
            if ("class".equals(name)) {
                continue; // No point in trying to set an object's class
            }
            if (Arrays.asList(exceptProperties).contains(name)) {
                continue;
            }
            if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) {
                try {
                    Object value = getPropertyUtils().getSimpleProperty(orig, name);
                    copyProperty(dest, name, value);
                } catch (NoSuchMethodException e) {
                    // Should not happen
                }
            }
        }
    }
}

Then you can use that method to copy all properties except the different ones:

import java.lang.reflect.InvocationTargetException;

public class B {

    // ...

    public static B fromA(A objA) {
        B objB = new B();

        // Copy common properties
        try {
            new MyBeanUtilsBean().copyPropertiesExcept(objB, objA, "d");
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        // Explicitly copy specific properties
        objB.setD(new IntWrapper(objA.getD()));

        return objB;
    }
}

You can also try a complete working example.

like image 33
Cos64 Avatar answered Oct 23 '22 12:10

Cos64