【问题标题】:how to fetch and validate csv header in open csv?如何在打开的 csv 中获取和验证 csv 标头?
【发布时间】:2019-05-09 09:58:27
【问题描述】:

我想从 csv 文件中获取标题。如果我不使用此 skipLines,那么我将在 0 index array 处获得标题。但我想直接使用 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;
}

【问题讨论】:

  • 是否必须使用 Open Csv 或任何其他验证标头的方式才可以

标签: java spring-boot opencsv


【解决方案1】:

我找到了以下解决方案:

  1. 将 @CsvBindByName 与 HeaderColumnNameMappingStrategy 一起使用,例如使用 @CsvBindByName 注释您的 bean 属性:
    public static class HollywoodActor {
        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 {
            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);
        }

别忘了添加公共类 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 文件中的实际标题。只需将它们与预期进行比较即可。

希望有帮助!

【讨论】:

    【解决方案2】:

    这里我将我的 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】:

      这是您当前问题的替代方法。首先,定义您希望标题的外观。例如:

      public static final ArrayList&lt;String&gt; fileFormat = new ArrayList&lt;&gt; (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
          //proceed
      

      【讨论】:

        【解决方案4】:

        尝试使用 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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-06-06
          • 1970-01-01
          • 1970-01-01
          • 2019-03-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-12
          相关资源
          最近更新 更多