Reputation: 115
I am trying to read a CSV file whose header position numbers comes from a property file.
I get the position number for the fields using @Value. But however I am unable to bind this value as the position for @CsvBindByPosition
.
Here is my code :
public class MyPojo {
@Value(value = "${csv.pojo.refNumber}")
public static final int test;
@CsvBindByPosition(position = test)
private String id;
}
This gives me this error:
The value for annotation attribute
CsvBindByPosition.position
must be a constant expression
Is there a way to resolve this as my position needs to be read from a property file itself?
Upvotes: 1
Views: 778
Reputation: 8114
As suggested by MWiesner answer, annotation value can't be set in runtime.
What we need is some way to configure the column position mapping in runtime instead.
MappingStrategy
is the interface configure mapping between csv and bean.ColumnPositionMappingStrategy
is the implementation ready for use in this case.import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.ColumnPositionMappingStrategyBuilder;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.StringReader;
@SpringBootTest(
classes = CsvByPositionInRuntimeTest.Pojo.class,
properties = {"csv.pojo.refNumber=0", "csv.pojo.name=1"}
)
public class CsvByPositionInRuntimeTest {
// assume 0
@Value("${csv.pojo.refNumber}")
private Integer refNumberIndex;
// assume 1
@Value("${csv.pojo.name}")
private Integer nameIndex;
// or specify column order like 'refNumber,name'
// this is more readable and easy to maintain
@Value("${csv.pojo.columnOrder}")
private String[] columnOrder;
@Test
public void parse() {
CSVReader csvReader = new CSVReaderBuilder(
new StringReader("""
123,david
456,terry
""")
).build();
ColumnPositionMappingStrategy<Pojo> positionMappingStrategy = new ColumnPositionMappingStrategyBuilder<Pojo>().build();
// this is just for demo, proper implementation is need for all column index
positionMappingStrategy.setColumnMapping(refNumberIndex < nameIndex ? new String[]{"refNumber", "name"} : new String[]{"name", "refNumber"});
// positionMappingStrategy.setColumnMapping(columnOrder);
positionMappingStrategy.setType(Pojo.class);
CsvToBean<Pojo> csvModelCsvToBean =
new CsvToBeanBuilder<Pojo>(csvReader).withMappingStrategy(
positionMappingStrategy
).build();
csvModelCsvToBean.parse().forEach(m -> System.out.println("id:%s ,name:%s".formatted(m.refNumber, m.name)));
}
public static class Pojo {
private String refNumber;
private String name;
}
}
Upvotes: 1
Reputation: 9043
The value for the annotation's parameter position
must be a compile time constant, so there is no way to achieve what you're aiming for.
Why? In the given code snippet, the value of the (pseudo-) constant is not (explicitly) declared at compile time. Hence, even if some value is injected at runtime, that will be too late. For further details, I'd recommend reading this blog entry.
In other words: The compiler can't ensure that test
has a known numerical value during compilation which it can use for replacement for compiling code that refers to it. Even worse, it issues another compilation error:
Variable
test
might not have been initialized.
That violates the idea of a constant as there is no known (initialization) value. More directly: you can't circumvent a programming languages' hard requirements.
In sum: Reconsider, whether a hard-coded position value for test
is suited for your application. At least with OpenCSV it seems to be necessary to define your data format for processing upfront, that is: statically.
Upvotes: 3