Home > Software design >  Trigger the setter method of a field based on the field's JsonProperty
Trigger the setter method of a field based on the field's JsonProperty

Time:06-30

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())

CodePudding user response:

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);
            }
    }
}
}

CodePudding user response:

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.

CodePudding user response:

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());
  • Related