如何在打开的csv中获取和验证csv标头?

时间:2019-05-09 09:58:28

标签: java spring-boot opencsv

我想从csv文件获取标头。如果我不使用此skipLines,则将在0索引数组处获得标头。但是我想直接使用HeaderColumnNameMappingStrategy获取标头,但不适用于我的代码。

我还想验证标题列列表(例如csv不允许包含额外的列)

我也检查了此How to validate the csv headers using opencsv,但这对我没有帮助。

@SuppressWarnings({ "unchecked", "rawtypes" })
public Map<String, Object> handleStockFileUpload(MultipartFile file, Long customerId) {
    Map<String, Object> responseMap = new HashMap<>();
    responseMap.put("datamap", "");
    responseMap.put("errormap", "");
    responseMap.put("errorkeys", "");

    List<Map<String, Integer>> list = new ArrayList<>();
    List<StockCsvDTO> csvStockList = new ArrayList<>();

    try {
        String fileName = new SimpleDateFormat("yyyy_MM_dd_HHmmss").format(new Date()) + "_" + file.getOriginalFilename();
        responseMap.put("filename", fileName);

        File stockFile = new File(productsUploadFilePath + fileName);
        stockFile.getParentFile().mkdirs();
        FileOutputStream fos = new FileOutputStream(stockFile);
        fos.write(file.getBytes());
        fos.close();


        CsvTransfer csvTransfer = new CsvTransfer();


        ColumnPositionMappingStrategy ms = new ColumnPositionMappingStrategy();
        ms.setType(StockCsv.class);

        Reader reader = Files.newBufferedReader(Paths.get(productsUploadFilePath + fileName));
        CSVReader csvReader =  new CSVReader(reader);

        CsvToBean cb = new CsvToBeanBuilder(reader)
          .withType(StockCsv.class)
          .withMappingStrategy(ms)
          .withSkipLines(1)
          .build();

       csvTransfer.setCsvList(cb.parse());
       reader.close();


       csvStockList = csvTransfer.getCsvList();

    } catch (Exception e) {
        e.printStackTrace();
        responseMap.put("status", "servererror");
    }

     responseMap.put("datamap", csvStockList);

    return responseMap;
}

4 个答案:

答案 0 :(得分:1)

我找到了以下解决方案:

  1. 将@CsvBindByName与HeaderColumnNameMappingStrategy一起使用,例如用@CsvBindByName注释您的bean属性:
    public static class HollywoodActor {
        @CsvBindByName //no 'column' value, same as property name
        private int id;
        @CsvBindByName(column = "First Name")
        private String firstName;
        @CsvBindByName(column = "Last Name")
        private String lastName;
    // getter / setter
    }
  1. 添加这样的方法:
    public class CsvParser {

        public <T> ParseResult<T> parseByPropertyNames(Reader csvReader, Class<T> beanClass) throws IOException {
            if (!hasCsvBindByNameAnnotatedProperty(beanClass)) {
                throw new IllegalArgumentException(
                    format("At least one of bean class=%s properties should be annotated with @%s", beanClass.getName(),
                            CsvBindByName.class.getName()));
            }
            CSVReader reader = new CSVReaderBuilder(csvReader).withCSVParser(new 
 CSVParserBuilder().build()).build();
            CsvToBean<T> bean = new CsvToBean();
            HeaderColumnNameMappingStrategy<T> mappingStrategy = new HeaderColumnNameMappingStrategy();
            mappingStrategy.setType(beanClass);
            bean.setMappingStrategy(mappingStrategy);
            bean.setCsvReader(reader);
            List<T> beans = bean.parse();
            return new CsvParseResult<>(mappingStrategy.generateHeader(), beans);
        }
...
    private boolean hasCsvBindByNameAnnotatedProperty(Class beanClass) {
        for (Field field : beanClass.getDeclaredFields()) {
            if (field.getAnnotation(CsvBindByName.class) != null) {
                return true;
            }
        }
        for(PropertyDescriptor d : PropertyUtils.getPropertyDescriptors(beanClass)) {
            if (d.getReadMethod() != null && d.getReadMethod().getAnnotation(CsvBindByName.class) != null) {
                return true;
            }
            if (d.getWriteMethod() != null && d.getWriteMethod().getAnnotation(CsvBindByName.class) != null) {
                return true;
            }
        }
        return false;
    }
}

也不要忘记添加公共类ParseResult

    public class ParseResult <T> {
      private final String[] headers;
      private final List<T> lines;
      // all-args constructor & getters
    }
  1. 在代码中使用然后使用它们:
    String csv = "Id,First Name,Last Name\n" + "1, \"Johnny\", \"Depp\"\n" + "2, \"Al\", \"Pacino\"";
    CsvParseResult<HollywoodActor> parseResult = parser
                .parseByPropertyNames(new InputStreamReader(new ByteArrayInputStream(csv.getBytes(StandardCharsets.UTF_8), HollywoodActor.class)));
  1. 从ParseResult.headers中可以获取.csv文件中的实际标题。只需将它们与预期的结果进行比较即可。

希望有帮助!

答案 1 :(得分:0)

这里是当前问题的替代方法。首先,定义您希望标题显示的内容。例如:

public static final ArrayList<String> fileFormat = new ArrayList<> (Arrays.asList("Values1", "Values2", "Values3", "Values4")); 现在,编写一个返回自定义错误(如果存在)的方法:

public String validateCsvFileDetails(MultipartFile file, Set<String> requiredHeadersArray) {
    Set<String> errors = new HashSet<>();
    try {
        InputStream stream = file.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        String headerLine = reader.readLine();
        if (Objects.isNull(headerLine))
            return "The file has no headers, please ensure it has the correct upload format";
        List<String> headersInFileList;
        String[] headersInFileArray;
        if (headerLine.contains(",")) {
            headersInFileArray = StringUtils.split(headerLine, ",");
            headersInFileList = Arrays.asList(headersInFileArray);
        } else//the headerline has only one headerfield
        {
            headersInFileList = Collections.singletonList(headerLine);
        }
        for (String header : requiredHeadersArray) {
            if (!headersInFileList.contains(header))
                errors.add("The file has the wrong header format, please ensure " + header + " header is present");
        }
        //if there are errors, return it
        if (!errors.isEmpty())
            return sysUtils.getStringFromSet(errors);
        //Ensure the csv file actually has values after the header, but don't read beyond the first line
        String line;
        int counter = 0;

        while ((line = reader.readLine()) != null) {
            counter++;
            if (counter > 0)
                break;
        }
        //if line is null return validation error
        if (Objects.isNull(line))
            return "Cannot upload empty file";
    } catch (Exception e) {
        logger.error(new Object() {
        }.getClass().getEnclosingMethod().getName(), e);
        return "System Error";
    }

    return null;

}

现在,您可以按以下方式验证文件头:

  String errors = validateCsvFileDetails(file, new HashSet<>(fileFormat));
     if (errors != null)
        return error
    //procceed

答案 2 :(得分:0)

在这里,我将csvHeader与originalHeader进行比较:

List<String> originalHeader = fileUploadUtility.getHeader(new StockCsv());

List<String> invalidHeader = csvHeader.stream().filter(o -> (originalHeader.stream().filter(f -> f.equalsIgnoreCase(o)).count()) < 1).collect(Collectors.toList());
            if(null != invalidHeader && invalidHeader.size() > 0 && invalidHeader.toString().replaceAll("\\[\\]", "").length() > 0) {
                msg = "Invalid column(s) : " + invalidHeader.toString().replace(", ]", "]") + ". Please remove invalid column(s) from file.";
                resultMap.put(1, msg);
            }


 public List<String> getHeader(T pojo) {
    // TODO Auto-generated method stub
    final CustomMappingStrategy<T> mappingStrategy = new CustomMappingStrategy<>();
    mappingStrategy.setType((Class<? extends T>) pojo.getClass());
    String header[] = mappingStrategy.generateHeader();
    List<String> strHeader = Arrays.asList(header);
    return strHeader;
  }

答案 3 :(得分:0)

尝试使用 captureHeader 作为预过滤器: ...

private class CustomHeaderColumnNameMappingStrategy<T> extends HeaderColumnNameMappingStrategy {
    private String[] expectedHeadersOrdered = {"Column1", "Column2", "Column3", "Column4", "Column5"};
    @Override
    public void captureHeader(CSVReader reader) throws IOException, CsvRequiredFieldEmptyException {
        String[] actualCsvHeaders = reader.peek();
        String actualHeader, expectedHeader;
        if (expectedHeadersOrdered.length > actualCsvHeaders.length) {
            throw new CsvRequiredFieldEmptyException("Missing header column.");
        } else if (expectedHeadersOrdered.length < actualCsvHeaders.length) {
            throw new IOException("Unexpected extra header column.");
        }
        // Enforce strict column ordering with index
        // TODO: you might want to employ simple hashMap, List, set, etc. as needed
        for (int i=0; i<actualCsvHeaders.length; i++) {
            actualHeader = actualCsvHeaders[i];
            expectedHeader = expectedHeadersOrdered[i];
            if ( ! expectedHeader.equals(actualHeader) ) {
                throw new IOException("Header columns mismatch in ordering.");
            }
        }

        super.captureHeader(reader); // Back to default processing if the headers include ordering are as expected
    }
}


    CustomHeaderColumnNameMappingStrategy yourMappingStrategy = new CustomHeaderColumnNameMappingStrategy<YourPOJO>();
    ourMappingStrategy.setType(YourPOJO.class);
    try {
        pojosFromCsv = new CsvToBeanBuilder<YourPOJO>(new FileReader(csvFile))
                .withType(YourPOJO.class)
                .withMappingStrategy(yourMappingStrategy)
                .build();
        pojosFromCsv.stream();

}

灵感来自Using captureHeader in OpenCSV