0

i'm developing a simple CRUD api but my post method is not working, everytime a send data with json format it throws this exception

{
    "title": "Cannot construct instance of `academy.devdojo.springboot2.requests.AnimePostRequestBody` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (PushbackInputStream); line: 2, column: 5]",
    "status": 400,
    "details": "JSON parse error: Cannot construct instance of `academy.devdojo.springboot2.requests.AnimePostRequestBody` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `academy.devdojo.springboot2.requests.AnimePostRequestBody` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: (PushbackInputStream); line: 2, column: 5]",
    "developerMessage": "org.springframework.http.converter.HttpMessageNotReadableException",
    "timestamp": "2022-01-13T14:45:34.9138659"
}

it only works when a send a simple string like "some name to be saved", not

{
  "name" : "some name to be saved"
}

here are my classes

ENTITY

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
public class Anime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotEmpty(message= "anime name cannot be empty")
    private String name;

}

REPO

public interface AnimeRepository extends JpaRepository<Anime, Long> {
    List<Anime> findByName(String name);
}

SERVICE

@Service
@RequiredArgsConstructor
public class AnimeService {

    private final AnimeRepository animeRepository;

    public Page<Anime> listAll(Pageable pageable) {

        return animeRepository.findAll(pageable);
    }

    public List<Anime> findByName(String name) {
        List<Anime> byName = animeRepository.findByName(name);
        if (byName.isEmpty()){
            throw new BadRequestException("Anime not Found");
        }
        return byName;
    }

    public Anime findByIdOrThrowBadRequestException(long id) {
        return animeRepository.findById(id)
                .orElseThrow(() -> new BadRequestException("Anime not Found"));
    }


    public Anime save(AnimePostRequestBody animePostRequestBody) {

        Anime anime = AnimeMapper.INSTANCE.toAnime(animePostRequestBody);

        return animeRepository.save(anime);
    }

    public void delete(long id) {

        animeRepository.delete(findByIdOrThrowBadRequestException(id));
    }

    public void replace(AnimePutRequestBody animePutRequestBody) {

        Anime savedAnime  = findByIdOrThrowBadRequestException(animePutRequestBody.getId());

        Anime anime = AnimeMapper.INSTANCE.toAnime(animePutRequestBody);

        anime.setId(savedAnime.getId());

        animeRepository.save(anime);
    }

    public List<Anime> listAllNonPageable() {
        return animeRepository.findAll();
    }
}

REST CONTROLLER

@RestController
@Api(tags = "Atividades Economicas")
@RequestMapping("animes")
@Log4j2
@RequiredArgsConstructor
public class AnimeController {
    private final DateUtil dateUtil;
    private final AnimeService animeService;

    @ApiOperation(value = "Listar todos")
    @GetMapping
    public ResponseEntity<Page<Anime>> list(Pageable pageable) {
        //log.info(dateUtil.formatLocalDateTimeToDatabaseStyle(LocalDateTime.now()));
        return ResponseEntity.ok(animeService.listAll(pageable));
    }

    @GetMapping(path = "/all")
    public ResponseEntity<List<Anime>> listAll() {
        //log.info(dateUtil.formatLocalDateTimeToDatabaseStyle(LocalDateTime.now()));
        return ResponseEntity.ok(animeService.listAllNonPageable());
    }
    @ApiOperation(value = "Listar por ID")
    @GetMapping(path = "/{id}")
    public ResponseEntity<Anime> findById(@PathVariable long id) {
        return ResponseEntity.ok(animeService.findByIdOrThrowBadRequestException(id));
    }

    @ApiOperation(value = "Encontrar pelo nome")
    @GetMapping(path = "/find")
    public ResponseEntity<List<Anime>> findByName(@RequestParam String name) {
        return ResponseEntity.ok(animeService.findByName(name));
    }


    @PostMapping
    public ResponseEntity<Anime> save(@RequestBody @Valid AnimePostRequestBody animePostRequestBody) {
        return new ResponseEntity<>(animeService.save(animePostRequestBody), HttpStatus.CREATED);
    }


    @DeleteMapping(path = "/{id}")
    public ResponseEntity<Void> delete(@PathVariable long id) {
        animeService.delete(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    @PutMapping
    public ResponseEntity<Void> replace(@RequestBody AnimePutRequestBody animePutRequestBody) {
        animeService.replace(animePutRequestBody);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

DTO's

@Data
@Builder
public class AnimePostRequestBody {

   @NotEmpty(message= "anime name cannot be empty")
    private String name;

}

@Data
@Builder
public class AnimePutRequestBody {
    private Long id;
    private String name;
}

MAPPER

@Mapper(componentModel = "spring")
public abstract class AnimeMapper {

    public static  final AnimeMapper INSTANCE = Mappers.getMapper(AnimeMapper.class);

    public abstract Anime toAnime(AnimePostRequestBody animePostRequestBody);
    public abstract Anime toAnime(AnimePutRequestBody animePutRequestBody);
}

p.s. the put method is working fine although they are very similar.

4 Answers 4

2

@Data is a convenient shortcut annotation that bundles the features of @ToString, @EqualsAndHashCode, @Getter / @Setter and @RequiredArgsConstructor together

Default constructor is required. Add lombok's @NoArgsConstructor annotation.

Sign up to request clarification or add additional context in comments.

Comments

0

Along with the Model class, the DTO class also should contain @AllArgsConstructor and @NoArgsConstructor. To map RequestBody values to DTO it will need constructors and @Data doesn't provide @AllArgsConstructor and @NoArgsConstructor constructors.

Comments

0

//ENTITY

    @Data
    @Entity
    @Table(name = "Anime")
    @Builder
    public class Anime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotEmpty(message= "anime name cannot be empty")
    private String name;

}

//REPO

@Repository
public interface AnimeRepository extends JpaRepository<Anime, Long> {
}

//DTO

@Getter
public class AnimePostRequestBody {

    @NotEmpty(message = "anime name cannot by empty")
    private String name;
}

//CONTROLLER

@RestController
@RequestMapping("animes")
public class AnimeController {

    @Autowired
    private AnimeService animeService;

    @PostMapping
    public ResponseEntity<Anime> save(@RequestBody @Valid AnimePostRequestBody animePostRequestBody) {
        return new ResponseEntity<>(animeService.save(animePostRequestBody), HttpStatus.CREATED);
    }


}

//SERVICE

@Service
public class AnimeService {

    @Autowired
    private AnimeRepository animeRepository;

    public Anime save(AnimePostRequestBody animePostRequestBody) {
        Anime anime = Anime.builder()
                .name(animePostRequestBody.getName())
                .build();
        return animeRepository.save(anime);
    }
}

OUTPUT

{ "id": 4, "name": "Naruto" }

Comments

0

replace this line

 public ResponseEntity<Anime> save(@RequestBody @Valid AnimePostRequestBody animePostRequestBody){..

with this:

 public ResponseEntity<Anime> save(@Valid @RequestBody AnimePostRequestBody animePostRequestBody)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.