0

I went through link: Is it possible in Java to check if objects fields are null and then add default value to all those attributes? and implemented the same solution as below -

Note: I am using Swagger/Open API Specs (using springdoc-openapi-ui) and while making POST request all string fields are having default value as "string" which I really wanted to set it to null or space.

Any quick pointer ?

public static Object getObject(Object obj) {
        for (Field f : obj.getClass().getFields()) {
            f.setAccessible(true);
            try {
                if (f.get(obj) == "string") {
                    f.set(obj, null);
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                log.error("Error While Setting default values for String");
            }
        }
   return obj;
}

REST endpoints

@GetMapping(value = "/employees")
public ResponseEntity<PagedModel<EmployeeModel>> findEmployees(
        EmployeeDto geoDto,
        @Parameter(hidden=true) String sort,
        @Parameter(hidden=true) String order,
        @Parameter(hidden=true) Pageable pageRequest) {

    EmployeeDto dto = (EmployeeDto) CommonsUtil.getObject(geoDto);

    Page<CountryOut> response = countryService..............;
    PagedModel<EmployeeModel> model = employeePagedAssembler.toModel(response, countryOutAssembler);

    return new ResponseEntity<>(model, HttpStatus.OK);
}
Naman
  • 27,789
  • 26
  • 218
  • 353
PAA
  • 1
  • 46
  • 174
  • 282
  • Regarding your implementation of `getObject()` - please never use `==` to check for value equivalence: in Java `==` is weird and only checks value equivalence for "primitive types" (i.e. - not objects). `f.get(obj) == "string"` should be written as `Objects.equals(f.get(obj), "string")` (there are various other ways to check for equality, but this is the current best idiom). – Guss Apr 04 '20 at 21:20

1 Answers1

0

You could do it a bit simpler, I guess. If you control EmployeeDto, for example:

@Accessors(chain = true)
@Getter
@Setter
@ToString
static class EmployeeDto {

    private String firstname;
    private String lastname;
    private int age;

}

You could iterate over fields of the class and using MethodHandles invoke the needed setters, when some getters return the string you are interested in (and Strings are compared using equals, not ==). This can even be made into a tiny library. Here is a start:

private static final Lookup LOOKUP = MethodHandles.lookup();

/**
 * this computes all the know fields of some class (EmployeeDTO in your case) and their getter/setter
 */
private static final Map<Class<?>, Map<Entry<String, ? extends Class<?>>, Entry<MethodHandle, MethodHandle>>> ALL_KNOWN =
    Map.of(
        EmployeeDto.class, metadata(EmployeeDto.class)
    );
private Map<String, Entry<MethodHandle, MethodHandle>> MAP;

/**
 * For example this will hold : {"firstname", String.class} -> getter/setter to "firstname"
 */
private static Map<Entry<String, ? extends Class<?>>, Entry<MethodHandle, MethodHandle>> metadata(Class<?> cls) {
    return Arrays.stream(cls.getDeclaredFields())
                 .map(x -> new SimpleEntry<>(x.getName(), x.getType()))
                 .collect(Collectors.toMap(
                     Function.identity(),
                     entry -> {
                         try {
                             return new SimpleEntry<>(
                                 LOOKUP.findGetter(cls, entry.getKey(), entry.getValue()),
                                 LOOKUP.findSetter(cls, entry.getKey(), entry.getValue()));
                         } catch (Throwable t) {
                             throw new RuntimeException(t);
                         }
                     }
                 ));
}

With that information you can provide a public method for users to call, So you need to provide the actual instance of your DTO, the DTO class, the Class of the fields you want to "default to", the equality to check against and the actual defaultValue.

    public static <T, R> T defaulter(T initial,
                                  Class<T> dtoClass,
                                  Class<R> fieldType,
                                  R equality,
                                  R defaultValue) throws Throwable {

    Set<Entry<MethodHandle, MethodHandle>> all =
        ALL_KNOWN.get(dtoClass)
                 .entrySet()
                 .stream()
                 .filter(x -> x.getKey().getValue() == fieldType)
                 .map(Entry::getValue)
                 .collect(Collectors.toSet());

    for (Entry<MethodHandle, MethodHandle> getterAndSetter : all) {
        R whatWeGot = (R) getterAndSetter.getKey().invoke(initial);
        if (Objects.equals(whatWeGot, equality)) {
            getterAndSetter.getValue().invoke(initial, defaultValue);
        }
    }

    return initial;

}

And this is how your callers can call it:

public static void main(String[] args) throws Throwable {
    EmployeeDto employeeDto = new EmployeeDto()
        .setFirstname("string")
        .setLastname("string");

    EmployeeDto withDefaults = defaulter(employeeDto, EmployeeDto.class, String.class, "string", "defaultValue");

    System.out.println(withDefaults);
}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • @Martijn Pieters - Kindly un-comment my answer. This also works fine as I've tested it – PAA Apr 06 '20 at 05:13
  • Thanks, what if I want to set the default value null to all Integer fields ? – PAA Apr 06 '20 at 05:48
  • 1
    @PAA have you _read_ this answer or tried any of the code in it? it would be as simple as : `EmployeeDto integerDefaults = defaulter(employeeDto, EmployeeDto.class, Integer.class, null, 0);` for example – Eugene Apr 06 '20 at 13:09
  • Yes offcourse, I need to method simply to pass (employeeDto, EmployeeDto.class) ans inside get the details rather than specifically sending Integer or String – PAA Apr 06 '20 at 13:11
  • @PAA that would not make it extensible, at all, you do understand that, right? And if you really want just that - create another public abstraction on this private implementation. – Eugene Apr 06 '20 at 13:13