0

I have 3 entities: Book (parent), AntiqueBook (extends Book), ScienceJournal (extends Book).

The difference of AntiqueBook and ScienceJournal to Book is that each of them have an additional property than the parent (releaseYear and scienceindex, respectively).

Endpoint in the controller to create a new Book object:

@PostMapping("/books")
Book newBook(@RequestBody Book book) {
    return service.save(book);
}

The method in service:

public Book save(Book book) {
    return repository.save(book);
}

How can I create an AntiqueBook and ScienceJournal objects by only using this endpoint? For example, to check if @RequestBody has the property releaseYear (create AntiqueBook) or scienceIndex (create ScienceJournal). If it does not have either of these, then create Book.

For now, as a workaround, I created 2 additional endpoints to AntiqueBook and ScienceJournal, but I want to avoid this. I'm also having issues with the 'update' endpoint. If I can solve this one, then I can solve that one as well.

  • 1
    Is this a duplicate of https://stackoverflow.com/questions/30362446/deserialize-json-with-jackson-into-polymorphic-types-a-complete-example-is-giv ? – meriton Dec 22 '22 at 16:56

1 Answers1

3

You have to annotate your Book DTO with @JsonTypeInfo and @JsonSubtypes to let Jackson/Spring know how to unmarshal it.

Here's an example:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = AntiqueBook.class, name = "ANTIQUE"),
        @JsonSubTypes.Type(value = ScienceBook.class, name = "SCIENCE")
})
public class Book {

    private final String isbn;

    public Book(String isbn) {
        this.isbn = isbn;
    }

    public String getIsbn() {
        return isbn;
    }
}
public class AntiqueBook extends Book{

    private final String year;

    public AntiqueBook(String isbn, String year) {
        super(isbn);
        this.year = year;
    }

    public String getYear() {
        return year;
    }
}
public class ScienceBook extends Book{

    private final String author;

    public ScienceBook(String isbn, String author) {
        super(isbn);
        this.author = author;
    }

    public String getAuthor() {
        return author;
    }
}
@RestController
public class BookController {

    @PostMapping("/books")
    Book newBook(@RequestBody Book book) {
        return book;
    }

}
$>curl -X POST localhost:8080/books -d '{"isbn":"AAAA", "year":"2020", "type":"ANTIQUE"}' -H "Content-Type: application/json"

{"type":"ANTIQUE","isbn":"AAAA","year":"2020"}

$> curl -X POST localhost:8080/books -d '{"isbn":"AAAA", "author":"John", "type":"SCIENCE"}' -H "Content-Type: application/json"

{"type":"SCIENCE","isbn":"AAAA","author":"John"}
codependent
  • 23,193
  • 31
  • 166
  • 308
  • I would like to point out that @codependent is using the attribute `type` to delineate the different books. Which, I would encourage the OP to consider using the attribute `type` (as an enumeration or something like that) instead of inheritance for the different types of books in their system. Inheritance is used to add/override specialized behavior to the parent object and needs to be used judicously. I would argue that a science book has the exact same behavior as an antique book. But if this is a school project, I would ignore what was written in this comment. – hooknc Dec 22 '22 at 17:04
  • 1
    @codependent wow, that was a super fast answer, not expecting. Thank you for that. I got it to work with only one endpoint with your solution :) But now, I can't create object from Book entity. I get the error: "JSON parse error: Could not resolve subtype of missing type id property 'type'". How to not make 'type' mandatory? Only to include in the body when I want to create AntiqueBook or ScienceJournal. – EdgarRAlves Dec 22 '22 at 17:45
  • @hooknc Thanks for your input. I'm using inheritance here because AntiqueBook and ScienceJournal have different methods to calculate the total price, so they're overriding the method in Book (I did not mention this because I thought it would not be relevant for the problem). – EdgarRAlves Dec 22 '22 at 17:48
  • I'll go ahead and answer my question from the previous comment. When creating object from Book entity, just need to add in the request body "type" = "Book". – EdgarRAlves Dec 22 '22 at 17:55
  • @EdgarRAlves, understood on your price calculation. A gentle idea is to use the [Strategy Design Pattern](https://en.wikipedia.org/wiki/Strategy_pattern). Using that pattern could help from having to create a new type of object for every different book you have. – hooknc Dec 22 '22 at 19:55
  • 1
    @hooknc thanks, I will take a look at this pattern and check if I can refractor – EdgarRAlves Dec 22 '22 at 21:21