Farvardin
Farvardin

Reputation: 5424

Jackson CSV modeler for composite class

Consider Person and Address classes as:

class Person {

    String name;
    int age;
    Address first;
    Address second;
}

class Address {

    String country;
    String city;

}

I want to parse csv file to Person objects. sample csv file is:

NAME,AGE,COUNTERY_1,CITY_1,COUNTRY_2,CITY_2
john,50,USA,Arizona,UK,London
bob,27,France,paris,USA,Felorida

I write a model method:

CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
CsvMapper mapper = new CsvMapper();
MappingIterator<Person> readValues = mapper
            .readerWithTypedSchemaFor(Person.class)
            .with(bootstrapSchema)
            .readValues(file);
return readValues.readAll();

How to add Address class mapping to mapper?

Upvotes: 2

Views: 665

Answers (1)

Jeronimo Backes
Jeronimo Backes

Reputation: 6289

You can't really do this with Jackson, but univocity-parsers can with its @Nested annotation:

First define your Address class like this:

public static class Address {
    @Parsed
    String country;
    @Parsed
    String city;

    @Override
    public String toString() {
        return "Address{country='" + country + '\'' + ", city='" + city + '\'' + '}';
    }
}

Now if you look at your input, we need to somehow associate the headers "country_1" and "city_1" with the fields in your Address class. This is done using a custom HeaderTransformer class that appends the index to the header name. Let's define it like this:

public static class SuffixAppender extends HeaderTransformer {

    private String suffix;

    public SuffixAppender(String... args) {
        suffix = args[0];
    }

    @Override
    public String transformName(Field field, String name) {
        return name + "_" + suffix;
    }
}

Now you can define your Person class with nested Address attributes:

public static class Person {
    @Parsed
    String name;

    @Parsed
    int age;

    @Nested(headerTransformer = SuffixAppender.class, args = "1")
    Address first;

    @Nested(headerTransformer = SuffixAppender.class, args = "2")
    Address second;

    @Override
    public String toString() {
        return "Person{name='" + name + '\'' + ", age=" + age + ", first=" + first + ", second=" + second + '}';
    }
}

The above instructs the parser to get the field names in Address and append the given suffix to it with your SuffixAppender. The result will be matched against the headers of your input.

Finally you are ready to parse. There are many ways to use the parser, but the simplest is:

String input = "" +
        "NAME,AGE,COUNTRY_1,CITY_1,COUNTRY_2,CITY_2\n" +
        "john,50,USA,Arizona,UK,London\n" +
        "bob,27,France,paris,USA,Florida";

CsvParserSettings settings = new CsvParserSettings(); //configure the parser
settings.detectFormatAutomatically(); //detects line separators and delimiters

//parse the input with the settings above, into a list of Person objects    
List<Person> personList = new CsvRoutines(settings).parseAll(Person.class, new StringReader(input));

Let's see the result with:

Person person1 = personList.get(0);
Person person2 = personList.get(1);
System.out.println(person1);
System.out.println(person2);

Output:

Person{name='john', age=50, first=Address{country='USA', city='Arizona'}, second=Address{country='UK', city='London'}}
Person{name='bob', age=27, first=Address{country='France', city='paris'}, second=Address{country='USA', city='Florida'}}

Hope it helps.

Disclaimer: I'm the author of this library. It's open source and free (Apache 2.0 license)

Upvotes: 2

Related Questions