记一次使用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; }
以上就是整个流程,希望对大家有帮助