arnaudroger / SimpleFlatMapper

Fast and Easy mapping from database and csv to POJO. A java micro ORM, lightweight alternative to iBatis and Hibernate. Fast Csv Parser and Csv Mapper
http://simpleflatmapper.org
MIT License
435 stars 76 forks source link

[Question] Generate dynamic header #685

Open Dam14n opened 4 years ago

Dam14n commented 4 years ago

Hi, there!

Is there a possibility to map a dynamic number of columns from a CSV to a list?

The only solution that I have found is to generate the headers dynamically depending on what I need but I don't know if there is a better solution.

Here an example of what I'm trying to do:

@Test
public void testCsvParser() throws Exception {    
    String csv = new StringBuilder()
        .append("StarWarsI~!~2~!~R2-D2~!~R2-D2" + System.lineSeparator())
        .append("StarWarsII~!~3~!~R2-D2" + System.lineSeparator())
        .append("StarWarsIII~!~4~!~R2-D2~!~R2-D2~!~R2-D2~!~R2-D2~!~R2-D2" + System.lineSeparator())
        .append("StarWarsIV~!~5~!~R2-D2~!~R2-D2~!~R2-D2~!~R2-D2" + System.lineSeparator())
        .append("StarWarsV~!~6~!~R2-D2~!~R2-D2~!~R2-D2" + System.lineSeparator())
        .toString();

    List<Film> films = new ArrayList<>();
    Arrays.stream(csv.split(System.lineSeparator())).forEach( line -> {
        line = line.replaceAll("~!~", ",");
        try {
            String[] headers = {"name", "id"};
            headers = this.generateDynamicHeaders(headers, line, 2);
            Film film = CsvParser
                .mapTo(Film.class)
                .headers(headers)
                .stream(line)
                .findFirst()
                .get();
            films.add(film);
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    films.forEach(System.out::println);        
}

private String[] generateDynamicHeaders(String[] currentHeaders, String line, int startIndex) {
    List<String> headers = Arrays.stream(currentHeaders).collect(Collectors.toList());
    List<String> lineAsList = Arrays.stream(line.split(",")).collect(Collectors.toList());
    for (int i = 0; i < lineAsList.size() - startIndex; i++) {
        headers.add("actors_" + i + "_name");
    }
    return headers.toArray(new String[headers.size()]);
}

public static class Film {

    private String name;
    private int id;
    private List<Actor> actors;

    public Film(int id, String name, List<Actor> actors) {
        this.id = id;
        this.name = name;
        this.actors = actors == null ? new ArrayList<>() : actors;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<Actor> getActors() {
        return actors;
    }

    @Override
    public String toString() {
        return "Film{ id='" + id + "\' name='" + name + "\' actors=" + actors.toString() + " }";
    }
}

public static class Actor{

    private String name;

    public Actor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Actor{ name='" + name + "\'}";
    }
}

Thanks a lot!

arnaudroger commented 4 years ago

there is a strategy to generate default headers but it does not support list as it can't know the size of the list in advance. there is also 2 ways of dealing with list. it's not currently very developped

arnaudroger commented 4 years ago

also made me realized the separator question was related tothe writer? in that case it would be quite straightforward to change it to accept strings