1

I have a class as follows:

public class MyClass {
   @JsonProperty("my_id")
   private String id;

   public getId() {
      return this.id;
   }

   public setId(String id) {
      this.id = id;
   }
}

I have another class where I have the JsonProperty [my_id] information with me. With this information, I would like to trigger the getId() and setId() methods.

public class AnotherClass {
   // jsonProperty value is "my_id"
   public myMethod(MyClass myClass, String jsonProperty, String newId) {
      // call the setter method setId(newId) of myClass
     // call the getter method getId() of myClass
   }
}

I do understand that this is a classic case of Reflection in Java but I am unable to implement the same even after going through hours of documentation and resources on reflection. Can someone please help me here?

Would it also be possible to use Jackson ObjectMapper or Google Gson to get the desired result?

Edit:

Two of the solutions provided by "@gmrm" and "@suraj tomar" does the intended task [Thank you both]. But, both solutions are forced to iterate over each of the available fields. Instead of iterating over all the "Fields", isn't there a way to simply fetch the Field I am looking for based on the JsonProperty name? As an example:

public void myMethod(MyClass myClass, String jsonProperty, String newId) throws IllegalAccessException {
    for (Field field : MyClass.class.getDeclaredFields()) {
        JsonProperty jsonPropAnnotation = field.getAnnotation(JsonProperty.class);
        if (jsonPropAnnotation != null)
            if (jsonPropAnnotation.value().equals(jsonProperty)) {
                field.setAccessible(true);
                field.set(myClass, newId);
            }
    }
}

The solution above works. Yet, I would like to avoid the loop below, if at all it is possible.

for (Field field : MyClass.class.getDeclaredFields())
mang4521
  • 742
  • 6
  • 22

3 Answers3

1

If you don't have message with you, in that case I think reflection only can help you and if you use reflection you even don't need to call setter method for setting value, you can use below code to set the value-

public void myMethod(MyClass myClass, String jsonProperty, String newId) throws IllegalAccessException {
    for (Field field : MyClass.class.getDeclaredFields()) {
        JsonProperty jsonPropAnnotation = field.getAnnotation(JsonProperty.class);
        if (jsonPropAnnotation != null)
            if (jsonPropAnnotation.value().equals(jsonProperty)) {
                field.setAccessible(true);
                field.set(myClass, newId);
            }
    }
}

Without iterating with for loop you cant set value but you can change approach where at application start up you can populate a static map like below and then in your application you can just get field from map and can set the value without iterating again over loop.

public class MainClass {
private static final Map<String, Field> FIELD_MAP = new HashMap<>();

public static void main(String[] args) throws IllegalAccessException {
    setFieldMap();
    MyClass myClass = new MyClass();
    myMethod(myClass, "my_id", "1234");
    System.out.println(myClass.getId());
}

public static void myMethod(MyClass myClass, String jsonProperty, String newId) throws IllegalAccessException {
    Field field = FIELD_MAP.get(jsonProperty);
    field.setAccessible(true);
    field.set(myClass, newId);
}

public static void setFieldMap() {
    for (Field field : MyClass.class.getDeclaredFields()) {
        JsonProperty ann = field.getAnnotation(JsonProperty.class);
        if (ann != null)
            if (null != ann.value()) {
                FIELD_MAP.put(ann.value(), field);
            }
    }
}
}
  • Yes, this is the kind of response I was looking for. Thank you. But, I do have a query on your resolution. Instead of looping over all the Fields, isn't there a way to simply fetch the Field I am looking for based on the JsonProperty name? – mang4521 Jun 29 '22 at 13:50
  • 1
    Without iterating with for loop you cant set value but you can change approach where at application start up you can populate a static map like below and then in your application you can just get field from map and can set the value without iterating again over loop. 'Map FIELD_MAP = new HashMap<>(); for (Field field : MyClass.class.getDeclaredFields()) { JsonProperty ann = field.getAnnotation(JsonProperty.class); if (ann != null) if (null != ann.value()) { FIELD_MAP.put(ann.value(), field); }}` –  Jun 29 '22 at 14:09
  • Yes I was actually thinking on similar lines. Would it be possible for you to update this code snippet of yours as part of the answer instead of adding it over here in the comments section? It would be useful for those who have a similar question as well. – mang4521 Jun 29 '22 at 14:13
  • I have updated my answer with sample code, please check once - https://stackoverflow.com/a/72802427/19443549 –  Jun 29 '22 at 14:15
  • And there is another question I have here. Is there a way to find the type of the **Field** object? Whether it is a String, Integer, List, Map etc? – mang4521 Jun 29 '22 at 14:26
  • 1
    Yes, you can use field.getType(); and it will give class object of corresponding type, i.e, java.lang.String in your case. –  Jun 29 '22 at 14:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/246050/discussion-between-mang4521-and-tomar). – mang4521 Jun 30 '22 at 06:51
  • Hi Tomar, do you mind taking a look at this: https://stackoverflow.com/questions/72811575/call-jsonproperty-setter-method-from-a-dependent-class ? – mang4521 Jun 30 '22 at 07:08
1

With reflection:

class AnotherClass {

    Class<JsonProperty> jacksonAnnotation = JsonProperty.class;

    public void myMethod(MyClass myClass, String jsonProperty, String newId) throws InvocationTargetException, IllegalAccessException {
        Class<MyClass> clazz = MyClass.class;
        Field[] fields = clazz.getDeclaredFields();
        Method[] methods = clazz.getDeclaredMethods();
        Method setter = null;
        Method getter = null;
        for(Field field : fields){
            if(field.isAnnotationPresent(jacksonAnnotation)){
                String value = field.getAnnotation(jacksonAnnotation).value();
                if(value.equals(jsonProperty)){
                    getter = getMethodForField(methods, field, "get");
                    setter = getMethodForField(methods, field, "set");
                    break;
                }
            }
        }

        //getId() result
        Object getResult = getter.invoke(myClass);

        //setId() execution
        setter.invoke(myClass, newId);

        // Your logic comes here
        System.out.println(getResult);
        System.out.println(myClass.getId());

    }

    private Method getMethodForField(Method[] methods, Field field, String name) {
        for(Method method : methods){
            String methodName = method.getName().toLowerCase();
            String getterName = String.format("%s%s", name, field.getName()).toLowerCase();
            if(methodName.equals(getterName)){
                return method;
            }
        }
        return null;
    }

}

This code will find the matching getter and setter for a field matching the JsonProperty and use them.

So the following execution:

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        MyClass myClass = new MyClass();
        myClass.setId("oldId");
        new AnotherClass().myMethod(myClass, "my_id", "newId");
    }

Will output:

oldId
newId

Edit:

Based on this answer, you could use Jacksons merge functionality to achieve the same and avoid a loop on fields:

    public void myMethod(MyClass myClass, String jsonProperty, String newId) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectReader updater = mapper.readerForUpdating(myClass);
        MyClass updated = updater.readValue(String.format("{ \"%s\":\"%s\" }", jsonProperty, newId));
        System.out.println(updated.getId());
    }

Though I don't know the performance impacts it might have since it requires transforming data to and from json.

gmrm
  • 93
  • 6
  • Thank you for the very detailed answer. This is pretty much what I was looking out for. But I do have a question here. **Instead of iterating over all the "Fields", isn't there a way to simply fetch the Field I am looking for based on the JsonProperty name?** – mang4521 Jun 29 '22 at 13:55
  • You must iterate over the fields to check the value in the JsonProperty annotation. I am not aware of a way to fetch fields by the annotation value. Though you don't need to iterate over methods if you know all the inputs it requires. – gmrm Jun 29 '22 at 13:58
  • Sure. Let me dive a little more into your solution. Maybe, I might find a way to avoid the loop. Anyways, thank you and much appreciated. – mang4521 Jun 29 '22 at 14:06
  • 1
    This answer could help https://stackoverflow.com/questions/9895041/merging-two-json-documents-using-jackson. Updated my sample to apply to your code. – gmrm Jun 29 '22 at 14:20
0

You can use reflections but best approach will be to use jackson libraries (jackson-core and jackson-databind) because these libraries will give you performance benefits as well.

Below is small piece of code by using jackson libraries that you can refer -

    ObjectMapper mapper = new ObjectMapper();
    String message = "{\"my_id\":\"1234\"}";
    MyClass myClass = mapper.readValue(message,MyClass.class);
    System.out.println(myClass.getId());

You can use Gson as well if you want but @JsonProperty wont work for Gson, there is different annotaion in Gson corresponding to this @SerializedName("my_id"). Below is sample code for Gson as well -

public class MyClass {

    @SerializedName("my_id")
    private String id;
    public String getId() {
        return this.id;
    }
    public void setId(String id) {
        this.id = id;
    }
}

String message = "{\"my_id\":\"1234\"}";
MyClass myClass = new Gson().fromJson(message,MyClass.class);
System.out.println(myClass.getId());
  • Thank you for your response. Unfortunately my question is quite different. I only have the name of the JsonProperty "my_id" with me. I already have the MyClass object with me as well. Based on the jsonProperty value I receive as a parameter, I would like to call the corresponding getter and setter from the myClass object. It is not serialisation or deserialisation that I am looking for here. – mang4521 Jun 29 '22 at 13:19
  • Jackson libs will do the same thing, based on @JsonProperty, it will call corresponding variable's setter method to set the value. You can add any print statement in setter method and can try my first approach to be sure. –  Jun 29 '22 at 13:24
  • No the point here is that I *do not have the message* in the form of string with me as described by you. I only have the JsonProperty name as a string with me. I also already have the myClass object with me. All I wan't to do is call the myClass object's setId() and getId() methods using the the jsonProperty name. So your solution is not really what I am looking for out here. – mang4521 Jun 29 '22 at 13:28