Copy all values from fields in one class to another through reflection
Asked Answered
C

19

97

I have a class which is basically a copy of another class.

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

What I am doing is putting values from class A into CopyA before sending CopyA through a webservice call. Now I would like to create a reflection-method that basically copies all fields that are identical (by name and type) from class A to class CopyA.

How can I do this?

This is what I have so far, but it doesn't quite work. I think the problem here is that I am trying to set a field on the field I am looping through.

private <T extends Object, Y extends Object> void copyFields(T from, Y too) {

    Class<? extends Object> fromClass = from.getClass();
    Field[] fromFields = fromClass.getDeclaredFields();

    Class<? extends Object> tooClass = too.getClass();
    Field[] tooFields = tooClass.getDeclaredFields();

    if (fromFields != null && tooFields != null) {
        for (Field tooF : tooFields) {
            logger.debug("toofield name #0 and type #1", tooF.getName(), tooF.getType().toString());
            try {
                // Check if that fields exists in the other method
                Field fromF = fromClass.getDeclaredField(tooF.getName());
                if (fromF.getType().equals(tooF.getType())) {
                    tooF.set(tooF, fromF);
                }
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

I am sure there must be someone that has already done this somehow

Classis answered 3/11, 2009 at 14:58 Comment(3)
See also #1433264Hintze
Yeah or the BeanUtils from Apache Jakarta.Cheryl
See also How do I copy an object in Java?Comeon
A
120

If you don't mind using a third party library, BeanUtils from Apache Commons will handle this quite easily, using copyProperties(Object, Object).

Agnostic answered 3/11, 2009 at 15:7 Comment(3)
Apparently BeanUtils doesn't work with null Date fields. Use Apache PropertyUtils if this is a problem for you: mail-archive.com/[email protected]/msg02246.htmlKenley
This apparently does not work for private fields with no getter and setters. Any solution that works on directly with fields, rather than properties?Buttercup
It neither works with plain public fields without getters: #34263622Comeon
P
20

Why don't you use gson library https://github.com/google/gson

you just convert the Class A to json string. Then convert jsonString to you subClass (CopyA) .using below code:

Gson gson= new Gson();
String tmp = gson.toJson(a);
CopyA myObject = gson.fromJson(tmp,CopyA.class);
Ptosis answered 25/11, 2016 at 6:30 Comment(5)
Why generate another String which could also be large? There are better alternatives described as answers here. At least we (the industry) progressed from XML to json for string representations, but we still don't want everything passed to that string representation at any given chance ...Huntingdon
Please noted that the string is a side product when using reflection . Even through you did not save it !! This is a answer for java beginners, and aim to be in short and clean way. @HuntingdonPtosis
You'd still need reflection on both the source and destination objects, but now you're also introducing binary/text/binary formatting and parsing overhead.Zingaro
Pay attention if you use Proguard to obfuscate the code. If you use it, this code will not work.Spec
this is useful in my case thanksKelm
M
11

BeanUtils will only copy public fields and is a bit slow. Instead go with getter and setter methods.

public Object loadData (RideHotelsService object_a) throws Exception{

        Method[] gettersAndSetters = object_a.getClass().getMethods();

        for (int i = 0; i < gettersAndSetters.length; i++) {
                String methodName = gettersAndSetters[i].getName();
                try{
                  if(methodName.startsWith("get")){
                     this.getClass().getMethod(methodName.replaceFirst("get", "set") , gettersAndSetters[i].getReturnType() ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }else if(methodName.startsWith("is") ){
                            this.getClass().getMethod(methodName.replaceFirst("is", "set") ,  gettersAndSetters[i].getReturnType()  ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }

                }catch (NoSuchMethodException e) {
                    // TODO: handle exception
                }catch (IllegalArgumentException e) {
                    // TODO: handle exception
                }

        }

        return null;
    }
Maronite answered 15/11, 2011 at 7:38 Comment(3)
BeanUtils works just fine on private fields, as long as the getters/setters are public. Regarding performance, I haven't done any benchmarking, but I believe it does some internal caching of the bean's it has introspected.Agnostic
this will only work if the two beans have the same data type of fields.Bags
@To Kra It would work only if you have getter/setter for that field.Gradual
G
10

Here is a working and tested solution. You can control the depth of the mapping in the class hierarchy.

public class FieldMapper {

    public static void copy(Object from, Object to) throws Exception {
        FieldMapper.copy(from, to, Object.class);
    }

    public static void copy(Object from, Object to, Class depth) throws Exception {
        Class fromClass = from.getClass();
        Class toClass = to.getClass();
        List<Field> fromFields = collectFields(fromClass, depth);
        List<Field> toFields = collectFields(toClass, depth);
        Field target;
        for (Field source : fromFields) {
            if ((target = findAndRemove(source, toFields)) != null) {
                target.set(to, source.get(from));
            }
        }
    }

    private static List<Field> collectFields(Class c, Class depth) {
        List<Field> accessibleFields = new ArrayList<>();
        do {
            int modifiers;
            for (Field field : c.getDeclaredFields()) {
                modifiers = field.getModifiers();
                if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
                    accessibleFields.add(field);
                }
            }
            c = c.getSuperclass();
        } while (c != null && c != depth);
        return accessibleFields;
    }

    private static Field findAndRemove(Field field, List<Field> fields) {
        Field actual;
        for (Iterator<Field> i = fields.iterator(); i.hasNext();) {
            actual = i.next();
            if (field.getName().equals(actual.getName())
                && field.getType().equals(actual.getType())) {
                i.remove();
                return actual;
            }
        }
        return null;
    }
}
Gleeman answered 30/8, 2017 at 8:39 Comment(3)
I have created a similar solution. I cached a class to field names to field map.Pulcheria
The solution is nice but you're gonna have a problem in this line i.remove(). Even if you've created iterator you can't call remove on List's iterator. It should be ArrayListBennion
Farid, remove can't be a problem because collectFields() creates ArrayList objects.Gleeman
T
9

Spring has a built in BeanUtils.copyProperties method. But it doesn't work with classes without getters/setters. JSON serialization/deserialization can be another option for copying fields. Jackson can be used for this purpose. If you are using Spring In most cases Jackson is already in your dependency list.

ObjectMapper mapper     = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz        copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);
Throat answered 28/3, 2017 at 20:46 Comment(0)
B
6

My solution:

public static <T > void copyAllFields(T to, T from) {
        Class<T> clazz = (Class<T>) from.getClass();
        // OR:
        // Class<T> clazz = (Class<T>) to.getClass();
        List<Field> fields = getAllModelFields(clazz);

        if (fields != null) {
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    field.set(to,field.get(from));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

public static List<Field> getAllModelFields(Class aClass) {
    List<Field> fields = new ArrayList<>();
    do {
        Collections.addAll(fields, aClass.getDeclaredFields());
        aClass = aClass.getSuperclass();
    } while (aClass != null);
    return fields;
}
Blockbusting answered 30/1, 2016 at 15:10 Comment(2)
I don't think this doesn't work for custom objects. Only if you have a class with no parent and only primitive fieldsClassis
For covering super class fields I am using 'getAllModelFields' custom methodBlockbusting
H
6

This is a late post, but can still be effective for people in future.

Spring provides a utility BeanUtils.copyProperties(srcObj, tarObj) which copies values from source object to target object when the names of the member variables of both classes are the same.

If there is a date conversion, (eg, String to Date) 'null' would be copied to the target object. We can then, explicitly set the values of the date as required.

The BeanUtils from Apache Common throws an error when there is a mismatch of data-types (esp. conversion to and from Date)

Hope this helps!

Holdup answered 6/4, 2018 at 14:4 Comment(1)
This doesn't provide any additional information than accepted https://mcmap.net/q/118986/-copy-all-values-from-fields-in-one-class-to-another-through-reflection answerFaviolafavonian
F
5

The first argument to tooF.set() should be the target object (too), not the field, and the second argument should be the value, not the field the value comes from. (To get the value, you need to call fromF.get() -- again passing in a target object, in this case from.)

Most of the reflection API works this way. You get Field objects, Method objects, and so on from the class, not from an instance, so to use them (except for statics) you generally need to pass them an instance.

Forepeak answered 3/11, 2009 at 15:14 Comment(0)
H
4

Dozer

UPDATE Nov 19 2012: There's now a new ModelMapper project too.

Hintze answered 3/11, 2009 at 15:4 Comment(0)
K
3

I think you can try dozer. It has good support for bean to bean conversion. Its also easy to use. You can either inject it into your spring application or add the jar in class path and its done.

For an example of your case :

 DozerMapper mapper = new DozerMapper();
A a= new A();
CopyA copyA = new CopyA();
a.set... // set fields of a.
mapper.map(a,copyOfA); // will copy all fields from a to copyA
Kynewulf answered 17/5, 2012 at 10:40 Comment(0)
I
3
  1. Without using BeanUtils or Apache Commons

  2. public static <T1 extends Object, T2 extends Object>  void copy(T1     
    origEntity, T2 destEntity) throws IllegalAccessException, NoSuchFieldException {
        Field[] fields = origEntity.getClass().getDeclaredFields();
        for (Field field : fields){
            origFields.set(destEntity, field.get(origEntity));
         }
    }
    
Iatrogenic answered 16/4, 2015 at 9:15 Comment(2)
This is not a working solution but a good starting point. The fields need to be filtered to handle only the non-static and public fields that are present in both of the classes.Gleeman
Wouldn't this ignore fields in parent classes?Zingaro
D
2

Orika's is simple faster bean mapping framework because it does through byte code generation. It does nested mappings and mappings with different names. For more details, please check here Sample mapping may look complex, but for complex scenarios it would be simple.

MapperFactory factory = new DefaultMapperFactory.Builder().build();
mapperFactory.registerClassMap(mapperFactory.classMap(Book.class,BookDto.class).byDefault().toClassMap());
MapperFacade mapper = factory.getMapperFacade();
BookDto bookDto = mapperFacade.map(book, BookDto.class);
Downright answered 7/1, 2015 at 14:19 Comment(1)
This does not do what the question is asking for. SerializationUtils.clone() is going to give a new object of the same class. Additionally it only works on serializable classes.Paucity
C
2

If you have spring in the list of dependencies you can use org.springframework.beans.BeanUtils.

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html

Claudicant answered 10/11, 2015 at 11:15 Comment(0)
I
1

I solved the above problem in Kotlin that works fine for me for my Android Apps Development:

 object FieldMapper {

fun <T:Any> copy(to: T, from: T) {
    try {
        val fromClass = from.javaClass

        val fromFields = getAllFields(fromClass)

        fromFields?.let {
            for (field in fromFields) {
                try {
                    field.isAccessible = true
                    field.set(to, field.get(from))
                } catch (e: IllegalAccessException) {
                    e.printStackTrace()
                }

            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }

}

private fun getAllFields(paramClass: Class<*>): List<Field> {

    var theClass:Class<*>? = paramClass
    val fields = ArrayList<Field>()
    try {
        while (theClass != null) {
            Collections.addAll(fields, *theClass?.declaredFields)
            theClass = theClass?.superclass
        }
    }catch (e:Exception){
        e.printStackTrace()
    }

    return fields
}

}

Itch answered 9/9, 2019 at 16:34 Comment(0)
C
1
public static <T> void copyAvalableFields(@NotNull T source, @NotNull T target) throws IllegalAccessException {
    Field[] fields = source.getClass().getDeclaredFields();
    for (Field field : fields) {
        if (!Modifier.isStatic(field.getModifiers())
                && !Modifier.isFinal(field.getModifiers())) {
            field.set(target, field.get(source));
        }
    }
}

We read all the fields of the class. Filter non-static and non-final fields from the result. But there may be an error accessing non-public fields. For example, if this function is in the same class, and the class being copied does not contain public fields, an access error will occur. The solution may be to place this function in the same package or change access to public or in this code inside the loop call field.setAccessible (true); what will make the fields available

Covered answered 19/3, 2020 at 6:24 Comment(1)
While this code may provide a solution to the question, it's better to add context as to why/how it works. This can help future users learn, and apply that knowledge to their own code. You are also likely to have positive feedback from users in the form of upvotes, when the code is explained.Pilfer
M
1

Here is my solution, will cover the child class case:

/**
 * This methods transfer the attributes from one class to another class if it
 * has null values.
 * 
 * @param fromClass from class
 * @param toClass   to class
 */
private void loadProperties(Object fromClass, Object toClass) {
    if (Objects.isNull(fromClass) || Objects.isNull(toClass))
        return;
    
    Field[] fields = toClass.getClass().getDeclaredFields();
    Field[] fieldsSuperClass = toClass.getClass().getSuperclass().getDeclaredFields();
    Field[] fieldsFinal = new Field[fields.length + fieldsSuperClass.length];

    Arrays.setAll(fieldsFinal, i -> (i < fields.length ? fields[i] : fieldsSuperClass[i - fields.length]));

    for (Field field : fieldsFinal) {
        field.setAccessible(true);
        try {
            String propertyKey = field.getName();
            if (field.get(toClass) == null) {
                Field defaultPropertyField = fromClass.getClass().getDeclaredField(propertyKey);
                defaultPropertyField.setAccessible(true);
                Object propertyValue = defaultPropertyField.get(fromClass);
                if (propertyValue != null)
                    field.set(toClass, propertyValue);
            }
        } catch (IllegalAccessException e) {
            logger.error(() -> "Error while loading properties from " + fromClass.getClass() +" and to " +toClass.getClass(), e);
        } catch (NoSuchFieldException e) {
            logger.error(() -> "Exception occurred while loading properties from " + fromClass.getClass()+" and to " +toClass.getClass(), e);
        }
    }
}
Magyar answered 24/3, 2022 at 9:12 Comment(0)
U
0

I didn't want to add dependency to another JAR file because of this, so wrote something which would suit my needs. I follow the convention of fjorm https://code.google.com/p/fjorm/ which means that my generally accessible fields are public and that I don't bother to write setters and getters. (in my opinion code is easier to manage and more readable actually)

So I wrote something (it's not actually much difficult) which suits my needs (assumes that the class has public constructor without args) and it could be extracted into utility class

  public Effect copyUsingReflection() {
    Constructor constructorToUse = null;
    for (Constructor constructor : this.getClass().getConstructors()) {
      if (constructor.getParameterTypes().length == 0) {
        constructorToUse = constructor;
        constructorToUse.setAccessible(true);
      }
    }
    if (constructorToUse != null) {
      try {
        Effect copyOfEffect = (Effect) constructorToUse.newInstance();
        for (Field field : this.getClass().getFields()) {
          try {
            Object valueToCopy = field.get(this);
            //if it has field of the same type (Effect in this case), call the method to copy it recursively
            if (valueToCopy instanceof Effect) {
              valueToCopy = ((Effect) valueToCopy).copyUsingReflection();
            }
            //TODO add here other special types of fields, like Maps, Lists, etc.
            field.set(copyOfEffect, valueToCopy);
          } catch (IllegalArgumentException | IllegalAccessException ex) {
            Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
        return copyOfEffect;
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
        Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return null;
  }
Ulises answered 4/2, 2015 at 18:28 Comment(1)
Antipattern: Reinvent the wheelWills
A
0

Mladen's basic idea worked (thanks), but needed a few changes to be robust, so I contributed them here.

The only place where this type of solution should be used is if you want to clone the object, but can't because it is a managed object. If you are lucky enough to have objects that all have 100% side-effect free setters for all fields, you should definitely use the BeanUtils option instead.

Here, I use lang3's utility methods to simplify the code, so if you paste it, you must first import Apache's lang3 library.

Copy code

static public <X extends Object> X copy(X object, String... skipFields) {
        Constructor constructorToUse = null;
        for (Constructor constructor : object.getClass().getConstructors()) {
            if (constructor.getParameterTypes().length == 0) {
                constructorToUse = constructor;
                constructorToUse.setAccessible(true);
                break;
            }
        }
        if (constructorToUse == null) {
            throw new IllegalStateException(object + " must have a zero arg constructor in order to be copied");
        }
        X copy;
        try {
            copy = (X) constructorToUse.newInstance();

            for (Field field : FieldUtils.getAllFields(object.getClass())) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }

                //Avoid the fields that you don't want to copy. Note, if you pass in "id", it will skip any field with "id" in it. So be careful.
                if (StringUtils.containsAny(field.getName(), skipFields)) {
                    continue;
                }

                field.setAccessible(true);

                Object valueToCopy = field.get(object);
                //TODO add here other special types of fields, like Maps, Lists, etc.
                field.set(copy, valueToCopy);

            }

        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            throw new IllegalStateException("Could not copy " + object, e);
        }
        return copy;
}
Abdominal answered 19/9, 2019 at 21:35 Comment(0)
C
0
    public <T1 extends Object, T2 extends Object> void copy(T1 origEntity, T2 destEntity) {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.map(origEntity,destEntity);
    }

 <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.4.0</version>
        </dependency>
Catalyst answered 10/10, 2019 at 6:42 Comment(1)
This mapper.map has issues, in an entity it is not copying the Primary keysPoole

© 2022 - 2024 — McMap. All rights reserved.