Scarlett John
Scarlett John

Reputation: 115

CsvBindByPosition : value for annotation attribute must be a constant expression

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

Answers (2)

samabcde
samabcde

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.

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

MWiesner
MWiesner

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

Related Questions