記一次使用opencsv解析csv文件時碰到的坑
最近在開發過程中需要解析csv文件,公司用的解析工具是opencsv,在根據opencsv的官方文檔去解析時發現csv文件中含有繁體字,使用其自帶的CsvToBean來轉換會出現異常com.opencsv.exceptions.CsvRequiredFieldEmptyException: Number of data fields does not match number of headers.於是我這里想到的方法是使用CsvReader來讀取文件,然后通過反射來注入到bean中,這里做個記錄希望對大家有幫助
一、引入依賴包
<dependency> <groupId>com.opencsv</groupId> <artifactId>opencsv</artifactId> <version>4.4</version> </dependency>
二、具體代碼
1.自定義注解,基礎一點的就是只需要定義數據列標題名title、格式轉換convert,我這里是由於業務需要所以稍微復雜些
import java.lang.annotation.*; /** * <p> * 解析csv文件注解 * </p> * * @Author zlc0w01 * @Date 2020/4/20 10:15 * @Version 1.0 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CsvReadColmn { /** * 該列數據的標題名 */ String title(); /** * 是否需要加密 */ boolean encrypt() default false; /** * 讀csv文件字段綁定 * 綁定格式轉換類,字符串轉Object * @return */ Class<? extends AbstractConvertCsvBase> convert() default AbstractConvertCsvBase.Converter.class; /** * 轉換依賴字段,如有某個字段轉換需要依賴其他字段, * 可設置為依賴字段的title * @return */ String convertRelyColumn() default ""; }
2.接下來就是定義用於轉換的基類了,這里面可以自己定義,我這里定義的意思是轉換所有字段,去掉前面的單引號“'”,其他需要定義的轉換規則可以繼承這個類,然后重寫convert方法就行了
public abstract class AbstractConvertCsvBase { private static final String SPLIT = "'"; /** * 轉換 * @param params 參數中必須有key為"value" * @return */ public Object startConvert(Map<String,String> params){ String value = params.get("value"); if (StringUtils.isNotBlank(value) && SPLIT.equals(value.substring(0,1))){ value = value.substring(1); } if (StringUtils.isBlank(value)){ return null; } params.put("value",value); return convert(params); } /** * 轉換方法 * @param params * @return */ public abstract Object convert(Map<String,String> params); public static class Converter extends AbstractConvertCsvBase{ public static Converter newInstance() { return new Converter(); } @Override public Object convert(Map<String,String> params) { return params.get("value"); } } }
3.定義需要轉換的bean
public class GbInsurancePolicy implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @CsvReadColmn(title = "內部號碼") private String id; @CsvReadColmn(title = "出生日期",convert = CsvConvertStringToSimpleDate.class) private Date birthday; }
//上面說到定義轉換規則,這里拿出生日期舉例
public class CsvConvertStringToSimpleDate extends AbstractConvertCsvBase {
@SneakyThrows
@Override
public Object convert(Map<String, String> params) {
String value = params.get("value");
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
return sf.parse(value);
}
}
4.定義反射來解析csv文件
public List<T> readSpecialCsv(String filePath) throws Exception{ List<T> list = new ArrayList<>(); FileInputStream fr = new FileInputStream(filePath); UnicodeInputStream unicodeInputStream = new UnicodeInputStream(fr, true); String enc = unicodeInputStream.getEncodingFromStream(); InputStreamReader is = new InputStreamReader(unicodeInputStream, enc); CSVReader reader = new CSVReader(is); String [] nextLine; String[] header = reader.readNext(); while ((nextLine = reader.readNext()) != null) { if (nextLine.length < header.length){ continue; } T t = getTClass().newInstance(); Field[] fields = t.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Field fieldName = t.getClass().getDeclaredField(field.getName()); CsvReadColmn csvReadColmn = fieldName.getAnnotation(CsvReadColmn.class); if (null != csvReadColmn){ int columnPosition = Arrays.asList(header).indexOf(csvReadColmn.title()); String value = nextLine[columnPosition]; AbstractConvertCsvBase convert = csvReadColmn.convert().newInstance(); Map<String,String> params = new HashMap<>(); params.put("value",value); //是否有需要依賴某個字段來轉換的 if (StringUtils.isNotBlank(csvReadColmn.convertRelyColumn())){ int relyColumnPosition = Arrays.asList(header).indexOf(csvReadColmn.convertRelyColumn()); String relyColumn = nextLine[relyColumnPosition]; params.put("relyColumn",relyColumn); } Object obj = convert.startConvert(params); String methodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); Method m = t.getClass().getDeclaredMethod(methodName, fieldName.getType()); m.invoke(t, obj); } } list.add(t); } reader.close(); return list; }
以上就是整個流程,希望對大家有幫助