Menu

#68 CsvToBeanBuilder with multiple bean type

v1.0 (example)
closed
None
5
2019-08-22
2019-08-02
Omeniq
No

Hello!!
I realy like this lib as it saves much time and is realy easy to use. I'm making some Spring app where I'm uploading a file and reading it as a Stream. Now I want to map it to the beans. The problem is that I have abstract bean Item and 2 another beans Book and Magazine that extends Item.

My Beans:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "BOOK")
public class Book extends Item {

    @Column
    @CsvBindByPosition(position = 1)
    private String author;

}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "MAGAZINE")
public class Magazine extends Item {

    @Column
    @CsvBindByPosition(position = 1)
    private String magazineNumber;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
public abstract class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id", updatable = false, nullable = false)
    private long id;

    @Column(nullable = false)
    @CsvBindByPosition(position = 0)
    private String title;

    @Column(nullable = false)
    @CsvBindByPosition(position = 2)
    private int quantity;

    @Transient
    @CsvBindByPosition(position = 3)
    public String type;

}

And now in my Controlelr I want to read file with 4 columns:

Book Title,Book Author,10,B
Magazine title,Magazine number,34,M

Title - for both Item bean
Author - only for Book bean
MagazineNumber - only for Magazine bean
Quantity - for both Item bean
Type - depending on this columns been must be Book or Magazine.

And now my CsvToBeanBuilder looks like:

       List<Item> objects = new CsvToBeanBuilder<Item>(inputStreamReader)
                .withType(Magazine.class)
                .build()
                .parse();

Loop thru the objects and save to db.

        for(Item object : objects) {
            if (object.getType().equals("B")) {
                LOG.warn("Mag will be saved");
                bookService.saveBook((Book) object);
            } else if (object.getType().equals("M")) {
                LOG.warn("Magazine will be saved");
                magazineService.saveMagazine((Magazine) object);
            }
        }

But of course it does not work because cannot cast:

java.lang.ClassCastException: model.item.Magazine cannot be cast to model.item.Book

Is there any way to deal with it? I tried to add two .withType(Magazine.class) and .withType(Book.class) but it does not work ;-)

Thanks in advance for any hint!

Discussion

  • Scott Conway

    Scott Conway - 2019-08-04

    There is no easy answer to this one because you already cast your objects to Magazine.

    There are two ways around this I can see offhand: The easy/ugly way would be to make bookService take a magazine and create a book from that (calling appropriate constructor and/or setters).

    The less ugly way would be to make the Item parent class concrete with all columns, and have constructors in Book and Magazine take an Item, and then your reader would use withType(Item.class) and your if would have bookSerivce.saveBook(new Book(object)).

    OH! just thought of a third way! Make the Item class a concrete factory class, with all fields, that would have a method public Object generateItem() that would check the type from that construct the Book or Magazine class. Then your loop would look closer to the original:

        for(Item object : objects) {
            if (object.getType().equals("B")) {
                LOG.warn("Mag will be saved");
                bookService.saveBook((Book) object.generateItem());
            } else if (object.getType().equals("M")) {
                LOG.warn("Magazine will be saved");
                magazineService.saveMagazine((Magazine) object.generateItem());
            }
        }
    

    Hope one of those helps.

    Scott :)

     
  • Omeniq

    Omeniq - 2019-08-22

    Hi Scott, thanks for your answer. I have resolved the problem thanks to your advice. I have created a another class with fileds from both enities. Something like this, it might be useful for someone with similar issue :)

    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class CSVItem {

    @CsvBindByPosition(position = 0, required = true)
    private String title;
    
    @CsvBindByPosition(position = 1, required = true)
    private String authorOrMagazineNumber;
    
    @CsvBindByPosition(position = 2, required = true)
    private int quantity;
    
    @CsvCustomBindByPosition(position = 3, converter = TextToEnum.class, required = true)
    public ItemType type;
    

    }

     
  • Andrew Rucker Jones

    • status: open --> closed
    • assigned_to: Scott Conway
     

Log in to post a comment.

MongoDB Logo MongoDB