Search code examples
mappingmultipartform-datamicronaut

Micronaut does not map an object in a multipart


So.. I have a method that consumes multipart/form-data. I'm trying to pass an object named User (ignoring some fields), and user avatar file


@ExecuteOn(TaskExecutors.IO)
@Operation(summary = "Endpoint for user registration")
@Post(uri = "/register",consumes = {MediaType.MULTIPART_FORM_DATA},produces = MediaType.APPLICATION_JSON)
@Requires(bean = User.class)
public HttpResponse<DefaultAppResponse> register(
        @Part("credential")  User credential,
        @Part("avatar") @Nullable CompletedFileUpload avatar
){
    try {
        if(avatar != null) {
            Files saved = filesService.save(avatar, dirPattern + avatars);
            saved.setOid(transactionalRepository.genOid().orElseThrow());
            filesRepository.save(saved);
            credential.setAvatarPath(saved);
        }
        credential.setUserRegDate(new Date(System.currentTimeMillis()));
        userRepository.save(credential);
        return HttpResponse.ok(
                errorService.success()).status(201
        );
    } catch (Exception e) {
        registerLog.error(e.getMessage(), e.getStackTrace());
        throw new InternalExceptionResponse("Error: " +e.getMessage() , errorService.error("error: " +e.getMessage()));
    }

}

User.class

@Entity
@Table(name = "users", schema = "public")
@Introspected
@JsonView(Default.class)
public class User extends BaseEntity{
    @JsonInclude
    @JsonProperty("user_name")
    @Column(name = "user_name")
    @JsonAlias("userName")
    private String userName;
    @JsonInclude
    @JsonProperty("user_birthday")
    @Column(name = "user_birthday")
    @JsonAlias("userBirthday")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date userBirthday;

    @JsonInclude
    @JsonFormat(pattern = "yyyy-MM-dd")
    @JsonProperty("user_reg_date")
    @Column(name = "user_reg_date")
    @JsonAlias("userRegDate")
    private Date userRegDate;

    @JsonInclude
    @JsonProperty("user_email")
    @Column(name = "user_email")
    @JsonAlias("userEmail")
    private String userEmail;


    @JsonProperty("user_password")
    @Column(name = "user_password")
    @JsonAlias("userPassword")
    @JsonView(WithPassword.class)
    private String userPassword;

    @ManyToOne
    @JoinColumn(name = "avatar_path")
    @JsonProperty("avatar_path")
    @JsonInclude
    @JsonAlias("avatarPath")
    private Files avatarPath;

    @JsonInclude
    @JsonProperty("user_phone_number")
    @Column(name = "user_phone_number")
    @JsonAlias("userPhoneNumber")
    private String userPhoneNumber;

    @JsonInclude
    @JsonProperty("user_is_confirm")
    @Column(name = "user_is_confirm")
    @JsonAlias("userIsConfirm")
    private Boolean userIsConfirm;

    public User(
            String oid, String userName,
            Date userBirthday, Date userRegDate, String userEmail, String userPassword, Files avatarPath,
            String userPhoneNumber, Boolean userIsConfirm
    ) {
        super(oid);
        this.userName = userName;
        this.userBirthday = userBirthday;
        this.userRegDate = userRegDate;
        this.userEmail = userEmail;
        this.userPassword = userPassword;
        this.avatarPath = avatarPath;
        this.userPhoneNumber = userPhoneNumber;
        this.userIsConfirm = userIsConfirm;
    }

    public User() {

    }

    public Files getAvatarPath() {
        return avatarPath;
    }

    public void setAvatarPath(Files avatarPath) {
        this.avatarPath = avatarPath;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        this.userBirthday = userBirthday;
    }

    public Date getUserRegDate() {
        return userRegDate;
    }

    public void setUserRegDate(Date userRegDay) {
        this.userRegDate = userRegDay;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    public Boolean getUserIsConfirm() {
        return userIsConfirm;
    }

    public void setUserIsConfirm(Boolean userIsConfirm) {
        this.userIsConfirm = userIsConfirm;
    }

    public String getUserPassword() {
        return userPassword;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }

    public String getUserPhoneNumber() {
        return userPhoneNumber;
    }

    public void setUserPhoneNumber(String userPhoneNumber) {
        this.userPhoneNumber = userPhoneNumber;
    }

}

But, When I trying to transfer an object, it simply cannot accept / parse / map it (underline correctly) What do I get in response

Request(Swagger)

Request Picture

Response

{
  "message": "Bad Request",
  "_links": {
    "self": {
      "href": "/api/reg/register",
      "templated": false
    }
  },
  "_embedded": {
    "errors": [
      {
        "message": "Required Part [credential] not specified",
        "path": "/credential"
      }
    ]
  }
}

If I remove @Part("credentails"), then it will contain an entity with fields equivalent to null. See screenshot

Debug Evaluation

Debug Evaluation

In my research, I saw that type:application/json is not being passed.But avatar file has type:image/png. See CURL request(generated from swagger)

curl -X 'POST' \
  'http://localhost:8080/api/reg/register' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'credential={
  "oid": "null",
  "avatar_path": "null"
  "user_reg_date": "null",
  "user_name": "User FUllName"
  "user_birthday": "2001-10-28",
  "user_email": "email@gmail.com",
  "user_password": "123123",
  "user_phone_number": "some-valid-phone-number"
}' \
  -F 'avatar=@sticker.png;type=image/png'

Question: what could be the problem?


Solution

  • I had same issue recently.
    The solution that I found (works fine):

    Request handler:

    @Post
    @Consumes(value = [MULTIPART_FORM_DATA])
    fun createOrganisation(
        principal: PrincipalUser,
        @Body request: RequestCreateOrg,
        @Part image: CompletedFileUpload? = null,
    )
    

    My dto:

    import com.fasterxml.jackson.databind.annotation.JsonDeserialize
    
    
    data class RequestCreateOrg(
        val name: String,
    
        @JsonDeserialize(converter = RequestCreateOrgOwnerConverter::class)
        val owner: RequestCreateOrgOwner? = null,
    )
    

    With implemented class RequestCreateOrgOwnerConverter : Converter<String, RequestCreateOrgOwner>