5

I'm wondering if there is a shorter version for checking if any field of my ProfileDto is blank.

Upon searching the internet, I only found questions about how to check if a field is null or if all fields are null which is something totally different.

For context, if blank, it should take the respective field of the user object (which is just a call to the database). If notBlank, then it should take the ProfileDto field

private void setEmptyFieldsForUpdatedUser(User user, ProfileDto profileDto) {
    String newFirstName = profileDto.getFirstName();
    String newLastName = profileDto.getLastName();
    String newEmailAdres = profileDto.getEmail();
    String oldPassword = profileDto.getPassword();
    String newPassword = profileDto.getNewPassword();

    if (newFirstName == null) {
        profileDto.setFirstName(user.getFirstName());
    }
    if (newLastName == null) {
        profileDto.setLastName(user.getLastName());
    }
    if (newEmailAdres == null) {
        profileDto.setEmail(user.getEmail());
    }
}

This ProfileDto gives a JSON object. Which can have null-values. If it is null, I want to set the value with the previous user field which is in my database.

My database user has the following properties:

firstname: dj
lastname : test
email : dj@mail.com
password : qwerty

Input example:

{
    "firstName": "freeman",
    "lastName": null,
    "email": null
    "password": null,
    "newPassword" : null
}

My output should become:

{
    "firstName": "freeman",
    "lastName": "test",
    "email": "dj@mail.com",
    "password": "qwerty"
}

Obviously, we can see that if I have 20 more variables that I need a lot of if's so I was wondering if there was a better way.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
DJ Freeman
  • 399
  • 2
  • 14

5 Answers5

4

Solution using JSON

As OP doesn't want to use reflection, another solution using jackson-databind dependency to do this with JSON:

Firstm we convert the class instances into a JsonTree. After that, we iterate over the fields of ProfileDto JSON, which also includes the null values and replace the null values with what we can find in the JSON for the User. We check that the user JSON has the field as to not cause exceptions. Finally, we convert the JsonTree back into an instance of ProfileDto:

ObjectMapper mapper = new ObjectMapper();
JsonNode userTree = mapper.valueToTree(user);
JsonNode dtoTree = mapper.valueToTree(dto);

Iterator<Entry<String, JsonNode>> fields = dtoTree.fields();
while (fields.hasNext()) {
    Entry<String, JsonNode> e = fields.next();
    if (e.getValue().isNull() && userTree.has(e.getKey())) {
        ((ObjectNode) dtoTree).put(e.getKey(), userTree.get(e.getKey()).asText());
    }
}
dto = mapper.treeToValue(dtoTree, ProfileDto.class);

I used following maven dependency:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

Solution using Java reflection

A possible solution includes reflection. That way you can check all fields by just iterating them. This solution however requires that both classes have the same field names, or at least that those in the DTO are present in the user class

User user = new User();
user.setFirstName("dj");
user.setLastName("test");
user.setEmail("dj@mail.com");
user.setPassword("qwerty");
ProfileDto dto = new ProfileDto();
dto.setFirstName("test");

System.out.println(user);
System.out.println(dto);
System.out.println();

for (Field f : dto.getClass().getDeclaredFields()) {
    if (f.get(dto) == null) {
        try {
            f.set(dto, user.getClass().getDeclaredField(f.getName()).get(user));
        } catch (NoSuchFieldException e) {
            System.out.println("Field missing: " + f.getName());
        }
    }
}

System.out.println(user);
System.out.println(dto);
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
XtremeBaumer
  • 6,275
  • 3
  • 19
  • 65
  • I think there are some unhandled cases: `User` might not have some of the fields in `ProfileDTO`, which would cause an exception and interrupt the execution, also `ProfileDTO` might have inherited fields. – Alexander Ivanchenko Dec 06 '22 at 16:16
  • That is exactly what I mentioned in my answer though. And with what OP has given as information, this solution does work exactly as he wants it – XtremeBaumer Dec 07 '22 at 07:16
  • Helle Xtreme, this works for the example above, however some information I didn't specify is that I have a password field in my User class and a oldPassword + newPassword field in the Dto, that makes it harder again. I'll update my question I didn't think that would be of any importance. – DJ Freeman Dec 07 '22 at 08:24
  • @DJFreeman updated the answer for missing fields in user class. The general idea stays the same, you just might need to adjust the logic for missing fields – XtremeBaumer Dec 07 '22 at 08:36
  • For some reason I'm getting the output Field missing: ... for all the fields even though I have set my first and lastname. My IDE is also saying that this bypass should be removed. :/ – DJ Freeman Dec 07 '22 at 09:17
  • Upon doing a System.out.println on f.get(dto) it returns nothing in console. Not even a line is added – DJ Freeman Dec 07 '22 at 09:23
  • Are there parent classes involved? – XtremeBaumer Dec 07 '22 at 09:31
  • No just User and ProfileDto which are not linked to eachother. In Postman I get the next error when adding your solution. " "message": "class com.reservationproject.services.AuthService cannot access a member of class com.reservationproject.dto.ProfileDto with modifiers "private" ". I'd like to avoid setting these to public since bypassing the accessibility fields with reflection violates the encapsulation principle. I was just hoping there would be an easy solution I overlooked :0. Anyways I really thank you for your time! – DJ Freeman Dec 07 '22 at 14:33
  • @DJFreeman I have updated my answer with a solution utilizing JSON. Check it out – XtremeBaumer Dec 07 '22 at 15:20
  • @DJFreeman we can also directly convert to instances into JsonTree with `mapper.valueToTree(user);`, which removes 2 lines and should increase performance a bit – XtremeBaumer Dec 07 '22 at 15:58
2

You can use the isBlank() method from the StringUtils class to check if a string is empty or contains only whitespace.

private void setEmptyFieldsForUpdatedUser(User user, ProfileDto profileDto) {
    String newFirstName = profileDto.getFirstName();
    String newLastName = profileDto.getLastName();
    String newEmailAdres = profileDto.getEmail();

    if (StringUtils.isBlank(newFirstName)) {
        profileDto.setFirstName(user.getFirstName());
    }
    if (StringUtils.isBlank(newLastName)) {
        profileDto.setLastName(user.getLastName());
    }
    if (StringUtils.isBlank(newEmailAdres)) {
        profileDto.setEmail(user.getEmail());
    }
}

This will check if any of the strings are empty or contain only whitespace, and if so, it will set the value of the corresponding field in the ProfileDto object to the value from the User object.

In case the number of variables increases, then to avoid checking explicitly for every variable, you can use the Map class to store the fields of the ProfileDto object and the corresponding setter methods for each field. Then you can just iterate over the Map and use the StringUtils.isBlank() method to check if a field is blank. If it is blank, you can use the setter method to set the corresponding field of the user object.

private void setEmptyFieldsForUpdatedUser(User user, ProfileDto profileDto) {
    Map<String, BiConsumer<ProfileDto, String>> setters = new HashMap<>();
    setters.put("firstName", ProfileDto::setFirstName);
    setters.put("lastName", ProfileDto::setLastName);
    setters.put("email", ProfileDto::setEmail);

    for (Map.Entry<String, BiConsumer<ProfileDto, String>> entry : setters.entrySet()) {
        String fieldName = entry.getKey();
        BiConsumer<ProfileDto, String> setter = entry.getValue();

        try {
            Method getter = ProfileDto.class.getMethod("get" + StringUtils.capitalize(fieldName));
            String fieldValue = (String) getter.invoke(profileDto);

            if (StringUtils.isBlank(fieldValue)) {
                Method userGetter = User.class.getMethod("get" + StringUtils.capitalize(fieldName));
                String userValue = (String) userGetter.invoke(user);
                setter.accept(profileDto, userValue);
            }
        } catch (Exception e) {
            
        }
    }
}
Vaibhav
  • 117
  • 7
  • Hi Vaibhav, This is true but doesn't shorten my code. If I have 10 more variables I'd have to write 10 more if statements. Thank you for your time – DJ Freeman Dec 07 '22 at 08:34
  • @DJFreeman I have updated the above answer. – Vaibhav Dec 07 '22 at 11:31
  • I was thinking of a way to do it like this and add the get's manually. However I'm just going to wait a few more days if there would be a less complicated answer! I appreciate the time you took for writing this! – DJ Freeman Dec 07 '22 at 14:17
1

I guess it's not possible to check if any field is empty without using reflection (which is not very elegant).

To make your code shorter and more readable I recommend you to use Objects.requireNonNullElse():

profileDto.setFirstName(Objects.requireNonNullElse(profileDto.getFirstName(), user.getFirstName()));
Felix Schildmann
  • 574
  • 1
  • 7
  • 22
  • Hello Felix, Thank you for your answer. I Don't think this will check for a String containing white spaces. That's why I have some context about isBlank in my question. Unfortunately there is no requireNonBlankElse. – DJ Freeman Dec 07 '22 at 08:32
1

If you hava java 9 or later you can minimize your code a bit with the following

Optional.ofNullable(profileDto.getFirstName()).or(() -> Optional.ofNullable(user.getFirstName())).ifPresent(profileDto::setFirstName);
 //...same for each field

This way

  • If Dto field and entity field are both null there would be no assignment.
  • If Dto field has non null value it will always remain as it's current value as the first operator would be true and reassigned to DTO.
  • If Dto field has null value and entity field not null, the entity field would be assigned to Dto.
Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
0

You could go with ternary expression, and just do it all in one line. (StringUtils class comes from apache commons)

String newFirstName = StringUtils.isBlank(profileDto.getFirstName()) ? user.getFirstName() : profileDto.getFirstName();
String newLastName = StringUtils.isBlank(profileDto.getLastName()) ? user.getLastName() : profileDto.getLastName();
String newEmailAdres = StringUtils.isBlank(profileDto.getEmail()) ? user.getEmail() : profileDto.getEmail();
Hawwaru
  • 5
  • 2
  • 5