Introduction

This page demonstrates the Expanded Context feature of the Object to CSV mapper. The Expanded Context is a space for adding new variable definitions during Object to CSV mapping.

This might be useful, for instance, if your Person Object has more than one address, and if the Person has an Address in MN, you want to include THAT address in your CSV and if the Person does not have a MN address, you want to include the first address.

Examples

Note that any CSV file should be valid as long as it complies with RFC 4180. If you wish to process non-compliant CSV files, you will have to wait until the next release of the CSV Object Mapper.

For the purposes of this demo, let's say that we have a CSV file with the following content:

 "First Name", "Last Name", "Street Address", "City", "State", "Zip Code", "Home Phone Number", "Cell Phone Number"
 "John", "Doe", "123 Test Dr.", "Test City", "HI", "11111", 1231231234, 5554443333
 "Jane", "Doe", "321 Tset Dr.", "Tset City", "IA", "99999", 4324324321, 3334445555

Note that the Java Objects to which you map your CSV may be POJOs employing the standard [set/get]ter properties. However, your Objects may be much more "fancy" if you like.

For the purposes of this demo, we want to map to a pair of Java Objects that look like this:

 public class Person {
        private String firstName;
        private String lastName;
        private Map<String, Address> addresses;
        private String homePhoneNumber;
        private String cellPhoneNumber;
        
        public void setFirstName(String firstName) {
                this.firstName = firstName;
        }
        
        public String getFirstName() {
                return firstName;
        }
        
        public void setLastName(String lastName) {
                this.lastName = lastName;
        }
        
        public String getLastName() {
                return lastName;
        }
        
        public Map<String, Address> getAddresses() {
                return addresses;
        }
        
        public void setAddresses(Map<String, Address> addresses) {
                this.addresses = addresses;
        }
        
        public void setHomePhoneNumber(String homePhoneNumber) {
                this.homePhoneNumber = homePhoneNumber;
        }
        
        public String getHomePhoneNumber() {
                return homePhoneNumber;
        }
        
        public void setCellPhoneNumber(String cellPhoneNumber) {
                this.cellPhoneNumber = cellPhoneNumber;
        }
        
        public String getCellPhoneNumber() {
                return cellPhoneNumber;
        }
 }
 
 public class Address {
        private String streetAddress;
        private String city;
        private String state;
        private Integer zipCode;
        
        public void setStreetAddress(String streetAddress) {
                this.streetAddress = streetAddress;
        }
        
        public String getStreetAddress() {
                return streetAddress;
        }
        
        public void setCity(String city) {
                this.city = city;
        }
        
        public String getCity() {
                return city;
        }
        
        public void setState(String state) {
                this.state = state;
        }
        
        public String getState() {
                return state;
        }
        
        public void setZipCode(Integer zipCode) {
                this.zipCode = zipCode;
        }
        
        public Integer getZipCode() {
                return zipCode;
        }
 }

We also have a pre-existing mapping file which we want to expand with mapping information for converting Java Objects into CSV. It looks like this:

  <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
        <bean id="csvToObjectMapper" class="com.projectnine.csvmapper.CsvToObjectMapper">
                <property name="csvMappingDefinitions">
                        <map>
                                <entry key="personMappingDefinition" value-ref="personMappingDefinition" />
                                <entry key="addressMappingDefinition" value-ref="addressMappingDefinition" />
                        </map>
                </property>
        </bean>
        <bean id="personMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition">
                <property name="fieldMappings">
                        <list>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setFirstName(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getFirstName()" />
                                        <property name="columnIndex" value="0" />
                                        <property name="csvFieldHeader" value="First Name" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setLastName(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getLastName()" />
                                        <property name="columnIndex" value="1" />
                                        <property name="csvFieldHeader" value="Last Name" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setAddress(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getAddress()" />
                                        <property name="beanName" value="addressMappingDefinition" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setHomePhoneNumber(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getHomePhoneNumber()" />
                                        <property name="columnIndex" value="6" />
                                        <property name="formatter" ref="phoneNumberFormatter" />
                                        <property name="validationCommand" ref="phoneNumberValidation" />
                                        <property name="csvFieldHeader" value="Home Phone Number" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setCellPhoneNumber(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getCellPhoneNumber()" />
                                        <property name="columnIndex" value="7" />
                                        <property name="formatter" ref="phoneNumberFormatter" />
                                        <property name="validationCommand" ref="phoneNumberValidation" />
                                        <property name="csvFieldHeader" value="Cell Phone Number" />
                                </bean>
                        </list>
                </property>
                <property name="beanClassName" value="Person" />
                <property name="expectedNumberOfFields" value="8" />
                <property name="beanVariableName" value="person" />
        </bean>
        <bean id="addressMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition">
                <property name="fieldMappings">
                        <list>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setStreetAddress(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getStreetAddress()" />
                                        <property name="columnIndex" value="2" />
                                        <property name="csvFieldHeader" value="Street Address" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setCity(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getCity()" />
                                        <property name="columnIndex" value="3" />
                                        <property name="csvFieldHeader" value="City" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setState(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getState()" />
                                        <property name="columnIndex" value="4" />
                                        <property name="csvFieldHeader" value="State" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setZipCode(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getZipCode()" />
                                        <property name="columnIndex" value="5" />
                                        <property name="formatter" ref="integerFormatter" />
                                        <property name="validationCommand" ref="numericalValidator" />
                                        <property name="csvFieldHeader" value="Zip Code" />
                                </bean>
                        </list>
                </property>
                <property name="beanClassName" value="Address" />
                <property name="expectedNumberOfFields" value="8" />
                <property name="beanVariableName" value="address" />
        </bean>
        
        <bean id="integerFormatter" class="com.projectnine.csvmapper.example.IntegerFormatter">
        </bean>
        <bean id="phoneNumberFormatter" class="com.projectnine.csvmapper.examples.PhoneNumberFormatter">
                <property name="separator" value="." />
        </bean>
        
        <bean id="tenCharacterValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator">
                <property name="regularExpressions">
                        <list>
                                <value>.{10}</value>
                        </list>
                </property>
                <property name="required" value="true" />
        </bean>
        <bean id="numericalValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator">
                <property name="regularExpressions">
                        <list>
                                <value>\d+</value>
                        </list>
                </property>
        </bean>
        <bean id="phoneNumberValidation" class="org.apache.commons.chain.impl.ChainBase">
                <constructor-arg>
                        <list>
                                <ref bean="tenCharacterValidator" />
                                <ref bean="numericalValidator" />
                        </list>
                </constructor-arg>
        </bean>
 </beans>

Requirements

We have artificially complicated our Person Object. Now, the Person Object has several Address Objects in a Map keyed by label (i.e. "home", "work", "mistress", etc.).

For some strange reason, we want to make sure that if a Person has a Minnesota Address, that Address should go into the CSV. Otherwise, the first Address will go into the CSV.

Configuration

We will change our objectToCsvExpressions for personMappingDefinition as follows:

 ...
        <bean id="personMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition">
                <property name="fieldMappings">
                        <list>
 ...
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setAddress(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="goodAddress" />
                                        <property name="beanName" value="addressMappingDefinition" />
                                </bean>
 ...
                        </list>
                </property>
                <property name="beanClassName" value="Person" />
                <property name="expectedNumberOfFields" value="8" />
                <property name="beanVariableName" value="person" />
        </bean>
 ...

Then, we configure the Expanded Context to define "goodAddress" according to our rules:

 ...
        <bean id="personMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition">
 ...
                <property name="extendedContext">
                        <map>
                                <entry key="goodAddress" value="person.getAddresses().values().iterator().next();" />
                                <entry key="goodAddress" value="foreach (address in person.getAddresses()) { if (address.getState == 'MN') { address; } } goodAdress;" />
                        </map>
                </property>
        </bean>
 ...

The notation is in JEXL.

See the "extendedContext" property of the "largeLocate" bean? It is simply a map of JEXL expressions keyed to the variable name to which the expression's result should be assigned.

The expressions are evaluated in the order in which they are defined, and subsequently executed expressions (whether executed in the extended context or in the Object to CSV mapping process) will have access to the previously executed expressions. For instance, the second "goodAddress" expression has access to (and indeed uses) the first "goodAddress" variable.

Our final configuration looks like this:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
        <bean id="csvToObjectMapper" class="com.projectnine.csvmapper.CsvToObjectMapper">
                <property name="csvMappingDefinitions">
                        <map>
                                <entry key="personMappingDefinition" value-ref="personMappingDefinition" />
                                <entry key="addressMappingDefinition" value-ref="addressMappingDefinition" />
                        </map>
                </property>
        </bean>
        <bean id="personMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition">
                <property name="fieldMappings">
                        <list>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setFirstName(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getFirstName()" />
                                        <property name="columnIndex" value="0" />
                                        <property name="csvFieldHeader" value="First Name" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setLastName(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getLastName()" />
                                        <property name="columnIndex" value="1" />
                                        <property name="csvFieldHeader" value="Last Name" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setAddress(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="goodAddress" />
                                        <property name="beanName" value="addressMappingDefinition" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setHomePhoneNumber(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getHomePhoneNumber()" />
                                        <property name="columnIndex" value="6" />
                                        <property name="formatter" ref="phoneNumberFormatter" />
                                        <property name="validationCommand" ref="phoneNumberValidation" />
                                        <property name="csvFieldHeader" value="Home Phone Number" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setCellPhoneNumber(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="person.getCellPhoneNumber()" />
                                        <property name="columnIndex" value="7" />
                                        <property name="formatter" ref="phoneNumberFormatter" />
                                        <property name="validationCommand" ref="phoneNumberValidation" />
                                        <property name="csvFieldHeader" value="Cell Phone Number" />
                                </bean>
                        </list>
                </property>
                <property name="beanClassName" value="Person" />
                <property name="expectedNumberOfFields" value="8" />
                <property name="beanVariableName" value="person" />
                <property name="extendedContext">
                        <map>
                                <entry key="goodAddress" value="person.getAddresses().values().iterator().next();" />
                                <entry key="goodAddress" value="foreach (address in person.getAddresses()) { if (address.getState == 'MN') { address; } } goodAdress;" />
                        </map>
                </property>
        </bean>
        <bean id="addressMappingDefinition" class="com.projectnine.csvmapper.CsvMappingDefinition">
                <property name="fieldMappings">
                        <list>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setStreetAddress(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getStreetAddress()" />
                                        <property name="columnIndex" value="2" />
                                        <property name="csvFieldHeader" value="Street Address" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setCity(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getCity()" />
                                        <property name="columnIndex" value="3" />
                                        <property name="csvFieldHeader" value="City" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setState(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getState()" />
                                        <property name="columnIndex" value="4" />
                                        <property name="csvFieldHeader" value="State" />
                                </bean>
                                <bean class="com.projectnine.csvmapper.CsvFieldMapping">
                                        <property name="csvToObjectExpression" value="setZipCode(%ARGUMENT%)" />
                                        <property name="objectToCsvExpression" value="address.getZipCode()" />
                                        <property name="columnIndex" value="5" />
                                        <property name="formatter" ref="integerFormatter" />
                                        <property name="validationCommand" ref="numericalValidator" />
                                        <property name="csvFieldHeader" value="Zip Code" />
                                </bean>
                        </list>
                </property>
                <property name="beanClassName" value="Address" />
                <property name="expectedNumberOfFields" value="8" />
                <property name="beanVariableName" value="address" />
        </bean>
        
        <bean id="integerFormatter" class="com.projectnine.csvmapper.example.IntegerFormatter">
        </bean>
        <bean id="phoneNumberFormatter" class="com.projectnine.csvmapper.examples.PhoneNumberFormatter">
                <property name="separator" value="." />
        </bean>
        
        <bean id="tenCharacterValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator">
                <property name="regularExpressions">
                        <list>
                                <value>.{10}</value>
                        </list>
                </property>
                <property name="required" value="true" />
        </bean>
        <bean id="numericalValidator" class="com.projectnine.csvmapper.RegularExpressionCsvFieldValidator">
                <property name="regularExpressions">
                        <list>
                                <value>\d+</value>
                        </list>
                </property>
        </bean>
        <bean id="phoneNumberValidation" class="org.apache.commons.chain.impl.ChainBase">
                <constructor-arg>
                        <list>
                                <ref bean="tenCharacterValidator" />
                                <ref bean="numericalValidator" />
                        </list>
                </constructor-arg>
        </bean>
 </beans>