Home > Mobile >  Java Reflection set enum in model
Java Reflection set enum in model

Time:03-29

I'm trying to update my DTO with Reflection.
The problem is that some fields in my DTO are enums and I get an error that I can not set the enum field to String.

DTO:

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Table(name = "xxx")
public class Model {

    @Id
    @Column(name = "id")
    private String runId;

    @Column(name = "name")
    private String name;


    @Column(name = "status")
    @Enumerated(EnumType.STRING)
    private ExecutionStatus status;


}


Controller:

 @PatchMapping(path = "/{id}", consumes = "application/json")
    public ResponseEntity<Void> partialUpdateModel(@PathVariable String id, @RequestBody Map<Object, Object> fields)
        throws Exception {

Optional<Model> model= service.getById(id);

        if (model.isPresent()) {
            fields.forEach((key, value) -> {
                Field field = ReflectionUtils.findField(Model.class, (String) key);
                field.setAccessible(true);
                ReflectionUtils.setField(field, model.get(), value);

            });

So when it comes to the enum field, the field can not be set. It says

cannot set ExecutionStatus to String

CodePudding user response:

What you are trying to do is this:

model.status = "STATUS_1";
// incompatible types: java.lang.String cannot be converted to so.A.ExecutionStatus

What you apparently want to do is finding the enum constant of the specified enum type with the specified string. That's what the Enum.valueOf or YourEnum.valueOf method is doing.

Example:

enum ExecutionStatus {
    STATUS_1,
    STATUS_2,
}

static class Model {
    public String runId;
    public String name;
    public ExecutionStatus status;

    @Override
    public String toString() {
        return "Model{"  
                "runId='"   runId   '\''  
                ", name='"   name   '\''  
                ", status="   status  
                '}';
    }
}

public static void main(String[] args) {
    Map<Object, Object> fields = Map.of(
            "runId", "MyRunId",
            "name", "MyName",
            "status", "STATUS_1"
    );

    Model model = new Model();

    fields.forEach((key, value) -> {
        Field field = null;
        try {
            field = Model.class.getDeclaredField((String) key);
            field.setAccessible(true);
            if (field.getType().isEnum()) {
                // First variant (YourEnum.valueOf(String)
                Method valueOf = field.getType().getMethod("valueOf", String.class);
                Object enumConstant = valueOf.invoke(null, value);
                field.set(model, enumConstant);

                // Alternative (Enum.valueOf(Class, String) (cast is safe due to isEnum)
                field.set(model, Enum.valueOf((Class<Enum>) field.getType(), (String) value));
            } else {
                field.set(model, value);
            }
        } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    });

    System.out.println(model);
}

CodePudding user response:

You can do like this, Also understand reflection is costly it involves types that are being dynamically resolved, i'd recommend writing setters or constructor instead

   if ("ExecutionStatus".equalsIgnoreCase(field.getType().getSimpleName())) {
                ReflectionUtils.setField(field, model.get(), ExecutionStatus.valueOf(value));
     } else {
                ReflectionUtils.setField(field, model.get(), value);
     }

CodePudding user response:

This is because your source map is of type <Object, Object>

What you try to do is to set a field of type ExecutionStatus reading a value of type Object. Types on your fields must match. First convert a value to ExecutionStatus then use the method .setField(..).

  • Related