I have a DTO like this,
ADto{
BDto bDto;
Cto cDto;
}
BDto{
String a1;
String b1;
int b1;
}
CDto{
String a2;
String b2;
int b2;
}
When I use reflect,I want to get the BDto and CDto in ADto Object.Code like this:
for (Field field : aObj.getClass().getDeclaredFields()) {
try {
Object fieldValue = field.get(object);
//todo how to collect all String value in `BDto` and `CDto` of aObj
if (fieldValue instanceof String) {
shouldCheckFieldValues.add((String) fieldValue);
}
} catch (Exception e) {
logger.error("some error has happened when fetch data in loop", e);
}
}
}
I want to collect all String value in BDto and CDto of aObj?How can I achieve this? Or how can I know the field which I have to recursive traversal with no hard code?
You directly try to get the String attributes from ADto class, you can't.
First get the BDto attribute, then retreive Strings attributes. Do the same for CDto attribute
for (Field field : aObj.getClass().getDeclaredFields()) {
try {
Object fieldValue = field.get(object);
//todo how to collect all String value in `BDto` and `CDto` of aObj
if (fieldValue instanceof BDto) {
for (Field field2 : fieldValue.getClass().getDeclaredFields())
if (field2 instanceof String) {
shouldCheckFieldValues.add((String) field2 );
Hope this helps
static void exploreFields(Object aObj) {
for (Field field : aObj.getClass().getDeclaredFields()) {
try {
Object instance_var = field.get(aObj);
if (instance_var instanceof String) {
System.out.println(instance_var);
} else if(!(instance_var instanceof Number)) {
exploreFields(instance_var);
}
} catch (Exception e) {
logger.error("some error has happened when fetch data in loop", e);
}
}
}
Edited based on comment. Be aware your objects should not have circular dependencies.
Related
I am creating java app which will allow storing objects in database. What I want to do is generic implementation so it could load json and create java class from it. This is what a code should look like:
SomeClass someObject= data.getValue(SomeClass.class);
Lets say that data would be a json object. How should I implement getValue() method so it will allow me to create class from it. I don't want SomeClass to extend anything other then Object. I think that this should be done using generic classes but so far I have not worked with generic classes like this. Can you please point to a best way on how to acomplish this? Example code would be best.
Many thanks
You can consult the source code of Jackson library and look inside (or debug) the method BeanDeserializer#vanillaDeserialize(), there you'll find the loop which traverse through all json tokens, finds the corresponding fields and sets their values.
As a proof of concept, I've extracted part of the logic from Jacskson and wrapped it inside a naive (and fragile) object mapper and a naive (and fragile) json parser:
public static class NaiveObjectMapper {
private Map<String, Object> fieldsAndMethods;
private NaiveJsonParser parser;
public <T> T readValue(String content, Class<T> valueType) {
parser = new NaiveJsonParser(content);
try {
// aggregate all value type fields and methods inside a map
fieldsAndMethods = new HashMap<>();
for (Field field : valueType.getDeclaredFields()) {
fieldsAndMethods.put(field.getName(), field);
}
for (Method method : valueType.getMethods()) {
fieldsAndMethods.put(method.getName(), method);
}
// create an instance of value type by calling its default constructor
Constructor<T> constructor = valueType.getConstructor();
Object bean = constructor.newInstance(new Object[0]);
// loop through all json nodes
String propName;
while ((propName = parser.nextFieldName()) != null) {
// find the corresponding field
Field prop = (Field) fieldsAndMethods.get(propName);
// get and set field value
deserializeAndSet(prop, bean);
}
return (T) bean;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private void deserializeAndSet(Field prop, Object bean) {
Class<?> propType = prop.getType();
Method setter = (Method) fieldsAndMethods.get(getFieldSetterName(prop));
try {
if (propType.isPrimitive()) {
if (propType.getName().equals("int")) {
setter.invoke(bean, parser.getIntValue());
}
} else if (propType == String.class) {
setter.invoke(bean, parser.getTextValue());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getFieldSetterName(Field prop) {
String propName = prop.getName();
return "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
}
class NaiveJsonParser {
String[] nodes;
int currentNodeIdx = -1;
String currentProperty;
String currentValueStr;
public NaiveJsonParser(String content) {
// split the content into 'property:value' nodes
nodes = content.replaceAll("[{}]", "").split(",");
}
public String nextFieldName() {
if ((++currentNodeIdx) >= nodes.length) {
return null;
}
String[] propertyAndValue = nodes[currentNodeIdx].split(":");
currentProperty = propertyAndValue[0].replace("\"", "").trim();
currentValueStr = propertyAndValue[1].replace("\"", "").trim();
return currentProperty;
}
public String getTextValue() {
return String.valueOf(currentValueStr);
}
public int getIntValue() {
return Integer.valueOf(currentValueStr).intValue();
}
}
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "id = " + id + ", name = \"" + name + "\"";
}
}
To see the deserialization in action run:
String json = "{\"id\":1, \"name\":\"jsmith\"}";
NaiveObjectMapper objectMapper = new NaiveObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
Or try online.
However I recommend not to reinvent the wheel and use Jackson and in case you need some custom actions you can use custom deserialization, see here and here.
BeanInfo componentBeanInfo = null;
List<String> propNames =new ArrayList<String>();
Object nestedObject=null;
try {
componentBeanInfo = Introspector.getBeanInfo(sourceObject.getClass());
final PropertyDescriptor[] props=componentBeanInfo.getPropertyDescriptors();
String [] parameters = getParameters(); //ObjectA.code="abc",ObjectA.type="single"
for (String parameter : parameters) {
boolean isNestedField = isNestedPropertyRead(parameter);
for(PropertyDescriptor prop : props){
if(isNestedField){
String[] fullParam = parameter.split("\\.");//ObjectA.code
String nestedObj = fullParam[0];//ObjectA
String nestObjField = fullParam[1];//code
if(nestedObj.equalsIgnoreCase(prop.getName())){
Class<?> classType = Class.forName(prop.getPropertyType().getName());
BeanInfo nestedBeanInfo;
nestedBeanInfo = Introspector.getBeanInfo(classType);
final PropertyDescriptor[] nestedProps =nestedBeanInfo.getPropertyDescriptors();
for(PropertyDescriptor nestedProp : nestedProps){
if(nestedProp.getName().equalsIgnoreCase(nestObjField)){
Class<?> nestedClassType = Class.forName(nestedProp.getPropertyType().getName());
Object value = convertToObject(nestedClassType,value(parameter));
try {
/*if(isNewProperty(prop.getName(),propNames)){
nestedObject = classType.newInstance();
}*/
if(nestedObject == null) {
nestedObject = classType.newInstance();
}
nestedProp.getWriteMethod().invoke(nestedObject, value);
prop.getWriteMethod().invoke(sourceObject,nestedObject );
} catch (IllegalArgumentException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
break;
}
}
break;
}
}
else if(prop.getName().equalsIgnoreCase(parameter)){
try {
Class<?> classType = Class.forName(prop.getPropertyType().getName());
Object value = convertToObject(classType,value(parameter));
prop.getWriteMethod().invoke(sourceObject, value);
break;
} catch (IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}catch (IntrospectionException | IllegalAccessException e) {
e.printStackTrace();
}
In above code snippet I'm facing issue w.r.t following lines -
nestedObject = classType.newInstance();
This creates new Instance every time and because of which i end up up
setting values to new nested objects each time overriding the
previous object. How can i avoid this and set values to already
created object in loop. Can anyone please suggest how can i get it
working?
I'll definitely be changing below piece of code also as generic one
rather than limiting to one instance of ' . ' as delimiter.But
thought of getting the looping logic right first.
String[] fullParam = parameter.split("\\.");//ObjectA.code
String nestedObj = fullParam[0];//ObjectA
String nestObjField = fullParam[1];//code
UPDATE-1- #javaguy -
My object structure looks like below-
public class OuterObject {
private String field1;
private ObjectA field2;
private ObjectB field3;
private String field4;
...// can have lot of nested objects of different types like ObjectA,ObjectB etc
}
public class ObjectA {
private String field1;
private int field2;
private String field3;
...
}
public class ObjectB {
private String field1;
private String field2;
private String field3;
...
}
As per your logic, nestedObject will have instance of ObjectA when
ObjectA's field (say field2) is encountered first.When we get ObjectB's field next (say field1)
we check if nestedObject is null and since it is not null we don't
create new instance for ObjectB.Because of this we end up setting
ObjectB's field value (field1) in ObjectA in following line-
nestedProp.getWriteMethod().invoke(nestedObject, value); This
results in error - java.lang.IllegalArgumentException: object is not
an instance of declaring class
you need to declare Object nestedObject=null; outside the loop and then create the instance only once by checking that it is null as shown below:
if(nestedObject == null) {
nestedObject = classType.newInstance();
}
I could make it work after adding following lines of code -
Object nestedObject = prop.getReadMethod().invoke(sourceObject);
if(nestedObject == null){
nestedObject = classType.newInstance();
}
I have created generic json parser using java reflection, but there is a error that i am not able to solve.
Method (at the bottom of this question), receives subclass of my custom Model class. I iterate through fields and set values from json. If subclass contains array property of some other class (which is again, subclass of Model), i've created small recursion to fill those objects.
Eg.
class UserModel extends Model
{
#JsonResponseParam(Name="userName")
public String Name;
#JsonResponseParam(Name="friends")
public FriendModel[] Friends;
}
At the end UserModel should be filled with Friends. (JsonResponseParam is custom anotation, and Name value is used as property for getting values from json)
Result of this method is IllegalArgumentException, and it is thrown on
field.set(t, values.toArray());
Here is the method:
protected <T extends Model> T getModel(T t)
{
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
Annotation an = field.getAnnotation(JsonResponseParam.class);
if(an != null){
try
{
if(field.getType() == boolean.class)
field.setBoolean(t, t.getBool(((JsonResponseParam)an).Name()));
if(field.getType() == String.class)
field.set(t, t.getString(((JsonResponseParam)an).Name()));
if(field.getType() == int.class)
field.setInt(t, t.getInt(((JsonResponseParam)an).Name()));
if(field.getType() == Date.class)
field.set(t, t.getDate(((JsonResponseParam)an).Name()));
if(field.getType().isArray()){
ArrayList<Model> modelArray = t.getModelArray(((JsonResponseParam)an).Name());
ArrayList<Model> values = new ArrayList<Model>();
for (Model model : modelArray) {
Class<? extends Model> arrayType = field.getType().getComponentType().asSubclass(Model.class);
Model m = arrayType.newInstance();
m.jsonObject = model.jsonObject;
model.getModel(m);
values.add(m);
}
field.set(t, values.toArray());
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
return t;
}
I am suspecting on class type inconsistency between field and values..
Thank you for your time.
toArray() only return an Object[] so it cannot be assigned to any other array type.
What you want is
field.set(t, values.toArray(Array.newInstance(field.getType().getComponentType(), values.size()));
This will create an array of the type to match the field.
See Array.newInstance
Basically, the pattern is as below:
// First, create the array
Object myArray = Array.newInstance(field.getType().getComponentType(), arraySize);
// Then, adding value to that array
for (int i = 0; i < arraySize; i++) {
// value = ....
Array.set(myArray, i, value);
}
// Finally, set value for that array field
set(data, fieldName, myArray);
The set function is taken from this stackoverflow question:
public static boolean set(Object object, String fieldName, Object fieldValue) {
Class<?> clazz = object.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, fieldValue);
return true;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
return false;
}
Apply the above code, we have:
if(field.getType().isArray()){
// ....
int arraySize = modelArray.size();
Object values = Array.newInstance(field.getType().getComponentType(), modelArray.size());
for (int i = 0; i < arraySize; i++) {
// ......
Array.set(values, i, m);
}
field.set(t, values);
}
I have a large list of java objects that all inherit from one shared object and each contains many field members (properties). However, not all fields on all of the objects are guaranteed to be initialized. There are also fields that are contained in the super class which should be included as well. I am looking to create a map that contains all of the initialized members with the member's identifier as the key and their value and the map value.
Is this even possible? I have looked briefly into reflection which looked promising but I do not have much experience with it. All of the values are primitive value types and could be stored in strings if necessary.
Use this
public void method method(Object obj) {
Map initializedFieldsMap = new HashMap();
for (Field field : obj.getClass().getDeclaredFields()) {
Boolean acessibleState = field.isAccessible();
field.setAccessible(true);
Object value;
try {
value = field.get(obj);
if (value != null) {
initializedFieldsMap.put(field.getName(), new WeakReference(value));
}
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
field.setAccessible(acessibleState);
}
return initializedFieldsMap;
}
It's using a WeakReference here so the object value won't get "stucked" (but it's still not ideal) and ineligible to GC, to access a value (String for instance) from the Map use:
String xxx = (String)map.get("value").get();
Combining the answers that I found:
public static Map<String, String> generatePropertiesMap(Object o)
{
Map<String, String> properties = new HashMap<String, String>();
for (Field field : getAllDeclaredFields(o)) {
Boolean acessibleState = field.isAccessible();
field.setAccessible(true);
Object value;
try {
value = field.get(o);
if (value != null) {
properties.put(field.getName(), value.toString());
}
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
field.setAccessible(acessibleState);
}
return properties;
}
private static List<Field> getAllDeclaredFields(Object o) {
List<Field> list = new ArrayList<Field>();
List<Field[]> fields = new ArrayList<Field[]>();
//work up from this class until Object
Class next = o.getClass();
while (true) {
Field[] f = next.getDeclaredFields();
fields.add(f);
next = next.getSuperclass();
if (next.equals(Object.class))
break;
}
for (Field[] f : fields) {
list.addAll(Arrays.asList(f));
}
return list;
}
In javascript, I can do this:
function MyObject(obj) {
for (var property in obj) {
this[property] = obj[property];
}
}
Can I do anything close in Java?
class MyObject {
String myProperty;
public MyObject(HashMap<String, String> props) {
// for each key in props where the key is also the name of
// a property in MyObject, can I assign the value to this.[key]?
}
}
Not that I disagree with Joel's answer, but I do not think it is not quite that difficult, if you essentially just want a best effort. Essentially check if it is there, and if it is try to set. If it works great if not, oh well we tried. For example:
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class MyObject {
protected String lorem;
protected String ipsum;
protected int integer;
public MyObject(Map<String, Object> valueMap){
for (String key : valueMap.keySet()){
setField(key, valueMap.get(key));
}
}
private void setField(String fieldName, Object value) {
Field field;
try {
field = getClass().getDeclaredField(fieldName);
field.set(this, value);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Map<String, Object> valueMap = new HashMap<String, Object>();
valueMap.put("lorem", "lorem Value");
valueMap.put("ipsum", "ipsum Value");
valueMap.put("integer", 100);
valueMap.put("notThere", "Nope");
MyObject f = new MyObject(valueMap);
System.out.println("lorem => '"+f.lorem+"'");
System.out.println("ipsum => '"+f.ipsum+"'");
System.out.println("integer => '"+f.integer+"'");
}
}
Yes, you can do it by reflection with something along the following lines:
/**
* Returns a list of all Fields in this object, including inherited fields.
*/
private List<Field> getFields() {
List<Field> list = new ArrayList<Field>();
getFields(list, getClass());
return list;
}
/**
* Adds the fields of the provided class to the List of Fields.
* Recursively adds Fields also from super classes.
*/
private List<Field> getFields(List<Field> list, Class<?> startClass) {
for (Field field : startClass.getDeclaredFields()) {
list.add(field);
}
Class<?> superClass = startClass.getSuperclass();
if(!superClass.equals(Object.class)) {
getFields(list, superClass);
}
}
public void setParameters(Map<String, String> props) throws IllegalArgumentException, IllegalAccessException {
for(Field field : getFields()) {
if (props.containsKey(field.getName())) {
boolean prevAccessible = field.isAccessible();
if (!prevAccessible) {
/*
* You're not allowed to modify this field.
* So first, you modify it to make it modifiable.
*/
field.setAccessible(true);
}
field.set(this, props.get(field.getName()));
/* Restore the mess you made */
field.setAccessible(prevAccessible);
}
}
}
However, if you are not very familiar with Java, this approach should be avoided if at all possible, as it is somewhat dangerous and error prone. For instance, there is no guarantee that the Field you are attempting to set are actually expecting a String. If it is the case that they are not, your program will crash and burn.
First, I would use a map if at all possible:
class MyObject {
// String myProperty; // ! not this
HashMap<String,String> myProperties; // use this instead
}
but let's say you wanted to set the fields dynamically.
public MyObject(HashMap<String, String> props) {
for (Map.Entry<String,String> entry : props.entrySet()) {
Field field = this.getClass().getField(entry.getKey());
field.set(this, entry.getValue());
}
}
of course, you will want to use a try/catch in the above constructor.
Well, if you really want to go down the reflection raod, then I suggest to have a look at the Introspector class and get the list of PropertyDescriptors from the BeanInfo.