10

Which approach requires the least amount of own written code to achieve a deep copy of one bean to another? The goal is to do it in an automatic way when source and target properties are matched by name.

source main bean:

public class SourceBean {
    private String beanField;
    private SourceNestedBean nestedBean;

    // getters and setters
}

source nested bean:

public class SourceNestedBean {
    private String nestedBeanField;

    // getters and setters
}

target main bean:

public class TargetBean {
    private String beanField;
    private TargetNestedBean nestedBean;

    // getters and setters        
}

target nested bean:

public class TargetNestedBean {
    private String nestedBeanField;

    // getters and setters
}


Using e.g. Spring BeanUtils.copyProperites() I could create a shallow copy of a SourceBean to TargetBean with one line of code but it will not copy nested beans. Is there any mature utility (not necessarily Spring Framework) that would allow to do the deep copy while writing as least own code as possible (pretty much same as BeanUtils.copyProperties())?

S. Pauk
  • 5,208
  • 4
  • 31
  • 41
  • Use *serialization* to do *deep copy* – TheLostMind Mar 27 '15 at 09:41
  • 1
    @TheLostMind I think that will work only if they are the same class. – RealSkeptic Mar 27 '15 at 09:43
  • @M.Deinum not quite. The question you mention is a more generic one (beans mapping) while what do I need is cloning `by filed name` in a very straightforward manner with the least amount of setup code possible – S. Pauk Mar 27 '15 at 09:48
  • Which is also what a mapping framework can do for you. It is mapping because you are using different beans, doesn't matter if it looks straightforward or not it it mapping nonetheless. – M. Deinum Mar 27 '15 at 09:48
  • @RealSkeptic - What do you mean *same class*?. I can create deep copies of composed objects using serialization – TheLostMind Mar 27 '15 at 09:59
  • 1
    @TheLostMind The copy is from an object of class `SourceBean` to an object of class `TargetBean`, which share no common ancestry. – RealSkeptic Mar 27 '15 at 10:22
  • 1
    @RealSkeptic you are correct, serialization in not an option since we are talking about two different classes – S. Pauk Mar 27 '15 at 10:24
  • Voting to reopen, this is a more specific case than the general case linked to as "duplicate". – Muhd May 30 '17 at 03:40

4 Answers4

19

One way to do it is with Jackson ObjectMapper, via the convertValue() method:

ObjectMapper mapper = new ObjectMapper();
SourceBean source = ...;
TargetBean target = mapper.convertValue(source, TargetBean.class);

Note that convertValue() is overloaded to also work with generic types. Also beware that convertValue() will in some circumstances return the value you provided, such as if SourceBean is assignable to TargetBean.

As Jackson is a JSON serialization/deserialization library, what convertValue() does is serialize source to JSON in memory, and then deserialize this JSON into an instance of TargetBean. So high performance is not to be expected. However, conversion is performed with one single line of code.

If you need performance, the best is to do the mapping manually. Nothing will be better than that.

If you need simplicity, use Jackson as explained above.

A good trade-off is Orika, a high performance mapper with almost no configuration that doesn't use reflection.

Community
  • 1
  • 1
fps
  • 33,623
  • 8
  • 55
  • 110
  • I suppose that could be accepted as an answer although I'm still not sure if i will use it in production because of possible performance issues. – S. Pauk Mar 28 '15 at 18:29
  • @SergeyPauk the only thing I can respond is that you should run some benchmark and see if times are reasonable for you. – fps Mar 28 '15 at 18:46
  • This is a bad answer, the convert option does not create an immutable bean. If a non primitive type is included in the bean, changing the target bean will change the source bean. – user666 Mar 27 '19 at 08:05
  • Yes i did. It changes the value in both beans – user666 Mar 27 '19 at 14:59
  • In my case it is not working. Your application works correctly, but mine no.. with same jackson version... after code inspection i was able to discover the reason. In my case i am copying same bean type: SourceBean target = mapper.convertValue(source, SourceBean.class); But in your case you are cloning to another bean type. cloning to same type (deep bean copy) is not immutable. So your answer works correctly for the question case but not to the other case (which was mine) – user666 Apr 03 '19 at 13:48
  • @user666 Now that's an interesting finding! I even think it's a bug. Do you want to [report it](https://github.com/FasterXML/jackson-databind/issues)? – fps Apr 03 '19 at 13:54
3

You can use Dozer Mapper to do deep copying. See http://dozer.sourceforge.net/documentation/deepmapping.html

Kirill Dubovikov
  • 1,457
  • 2
  • 21
  • 41
  • Is it possible to use Dozer w/o xml configuration and is it possible to define a map-by-name strategy because I don't want to define any particular mappings manually? – S. Pauk Mar 27 '15 at 09:54
  • You can use API mappings instead: http://dozer.sourceforge.net/documentation/apimappings.html. I think it will not be hard to write common helper method to copy your objects. – Kirill Dubovikov Mar 27 '15 at 09:59
  • Yes, this one doesn't use XML but I still have to define the fields to be copied so assuming I have dozens of beans that's unfortunately not an option. – S. Pauk Mar 27 '15 at 10:04
1

While if you want to use deep copy in Java, you should use ObjectOutputStream and ObjectInputStream and all class you need to copy should implements Serializable.

public Object deepCopy() throws IOException, ClassNotFoundException{
    //store object in memory using serial
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}
whjlou
  • 11
  • 2
-1

Use SerializationUtils from apache commons-lang. And make your object serializable.