java opencsv解析csv文件

记一次使用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;
    }

以上就是整个流程,希望对大家有帮助

posted @ 2020-04-24 16:52  一粒尘土丶  阅读(3524)  评论(0编辑  收藏  举报