Java的最佳设计解析具有不同标头和模型对象的多个CSV文件

时间:2018-02-15 10:47:01

标签: java parsing design-patterns abstract-class factory

我有几个我需要解析的CSV文件。并在以后用于MYSQL中的Insert。我已经写了一个解析器,但我想尽可能避免编码重复

我已经记住,我应该使用抽象类,或者可能是工厂,但我无法真正指出设计它的最佳方法。

所以这是我的解析器:

public class LocusAliasCsvParser {

private static final String[] FILE_HEADER_MAPPING = {"id", "locusID", "organismid", "variable", "alias"};

private static final String ID = "id";
private static final String LOCUS_ID = "locusID";
private static final String ORGANISM_ID = "organismid";
private static final String VARIABLE = "variable";
private static final String ALIAS = "alias";


public static List<AliasLocus> readCsvFile(String fileName) {

    FileReader fileReader = null;
    CSVParser csvFileParser = null;

    CSVFormat csvFileFormat = CSVFormat.DEFAULT.withHeader(FILE_HEADER_MAPPING);
    List<AliasLocus> aliases = new ArrayList();

    try {
        fileReader = new FileReader(fileName);
        csvFileParser = new CSVParser(fileReader, csvFileFormat);

        //Get a list of CSV file records
        List csvRecords = csvFileParser.getRecords();

        //Read the CSV file. Header is ignored (i == 1)
        for (int i = 1; i < csvRecords.size(); i++) {
            CSVRecord record = (CSVRecord) csvRecords.get(i);

            AliasLocus aliasLocus = new AliasLocus(Integer.parseInt(record.get(ID)),
                    record.get(LOCUS_ID),
                    record.get(ORGANISM_ID),
                    record.get(VARIABLE),
                    record.get(ALIAS));

            aliases.add(aliasLocus);
        }

    } catch (Exception e) {
        System.out.println("Error in CsvFileReader !!!");
        e.printStackTrace();
    } finally {
        try {
            fileReader.close();
            csvFileParser.close();
        } catch (IOException e) {
            System.out.println("Error while closing fileReader/csvFileParser !!!");
            e.printStackTrace();
        }
    }

    return aliases;
}

每次改变的事情是:

public class LocusAliasCsvParser {

private static final String[] FILE_HEADER_MAPPING = {"id", "locusID", "organismid", "variable", "alias"};

private static final String ID = "id";
private static final String LOCUS_ID = "locusID";
private static final String ORGANISM_ID = "organismid";
private static final String VARIABLE = "variable";
private static final String ALIAS = "alias";

和:

 public static List<AliasLocus> readCsvFile(String fileName) {

 AliasLocus aliasLocus = new AliasLocus(Integer.parseInt(record.get(ID)),
                    record.get(LOCUS_ID),
                    record.get(ORGANISM_ID),
                    record.get(VARIABLE),
                    record.get(ALIAS));

是否有人可以建议使用最佳设计模式或结构来进行最少的代码重复?

感谢

2 个答案:

答案 0 :(得分:1)

您应该使用接口分离不同的问题,并实现用于读取csv文件的模板方法。

让我们分6个步骤设置一个简单的框架。

  1. 您需要一个知道如何获取csv结构的类。

    public interface CsvMetadataSource {
         public CsvMetadata getCsvMetadata();
    }
    
  2. 您需要一个可以解析csv行的类。来自1的CsvMetadata是放置逻辑的好地方。

    public class CsvMetadata {
    
        private List<String> columns;
    
        public CsvMetadata(List<String> columns) {
            this.columns = columns;
        }
    
        public Map<String, String> parseLine(String line) {
            // simple implementation 
            String[] values = line.split(",");
    
            Map<String, String> record = new HashMap<>();
    
            for (int i = 0; i < columns.size(); i++) {
                String column = columns.get(i);
    
                String value = null;
    
                if (i < values.length) {
                    value = values[i];
                }
    
                record.put(column, value);
            }
    
            return record;
        }
    
    }
    
  3. 您需要一个可以将已解析的行映射到对象的类。知道行号也可能有用。

    public interface CsvRecordMapper<T> {
        public T map(Map<String, String> csvRecord, int lineNumber);
    }
    
  4. 您需要一个知道如何处理映射对象的类。

    public interface CsvObjectCallback<T> {
        public void process(T object);
    }
    
  5. 您需要一个实现模板方法的类来读取csv数据,并且可以通过上述接口的实现进行扩展。使用便捷方法将所有对象作为列表读取也可能是好事。

    public class CsvReader {
    
        private CsvMetadataSource csvMetadataSource = null;
    
        public CsvReader() {
        }
    
        public CsvReader(CsvMetadataSource csvMetadataSource) {
            this.csvMetadataSource = csvMetadataSource;
        }
    
        public <T> List<T> readAll(Reader csvInputReader, CsvRecordMapper<T> csvLineMapper) throws IOException {
            CollectCsvObjectCallback<T> collectCsvObjectCallback = new CollectCsvObjectCallback<>();
            read(csvInputReader, csvLineMapper, collectCsvObjectCallback);
            return collectCsvObjectCallback.getObjects();
        }
    
        public <T> void read(Reader csvInputReader, CsvRecordMapper<T> csvLineMapper,
                CsvObjectCallback<T> csvObjectCallback) throws IOException {
            try (BufferedReader lineReader = new BufferedReader(csvInputReader);) {
                CsvMetadataSource effectiveCsvMetadataSource = getCsvMetadataSource(lineReader);
    
                read(csvLineMapper, csvObjectCallback, lineReader, effectiveCsvMetadataSource);
            }
        }
    
        private CsvMetadataSource getCsvMetadataSource(BufferedReader lineReader) throws IOException {
            CsvMetadataSource effectiveCsvMetadataSource = csvMetadataSource;
            if (effectiveCsvMetadataSource == null) {
                String headerLine = lineReader.readLine();
                effectiveCsvMetadataSource = new RowBasedCsvMetadataSource(headerLine);
            }
            return effectiveCsvMetadataSource;
        }
    
        private <T> void read(CsvRecordMapper<T> csvLineMapper, CsvObjectCallback<T> csvObjectCallback,
                BufferedReader lineReader, CsvMetadataSource effectiveCsvMetadataSource) throws IOException {
            CsvMetadata effectiveCsvMetadata = effectiveCsvMetadataSource.getCsvMetadata();
            if (effectiveCsvMetadata != null) {
                String line;
                int csvRecordNumber = 0;
    
                while ((line = lineReader.readLine()) != null) {
                    Map<String, String> csvRecordValues = effectiveCsvMetadata.parseLine(line);
                    T object = csvLineMapper.map(csvRecordValues, csvRecordNumber++);
                    csvObjectCallback.process(object);
                }
            }
        }
    
    }
    
    class RowBasedCsvMetadataSource implements CsvMetadataSource {
    
        private String row;
    
        public RowBasedCsvMetadataSource(String row) {
            this.row = row;
        }
    
        @Override
        public CsvMetadata getCsvMetadata() {
            String[] columns = row.split(",");
            return new CsvMetadata(Arrays.asList(columns));
        }
    
    }
    
    class CollectCsvObjectCallback<T> implements CsvObjectCallback<T> {
    
        private List<T> objects = new ArrayList<>();
    
        @Override
        public void process(T object) {
            objects.add(object);
        }
    
        public List<T> getObjects() {
            return objects;
        }
    
    }
    
  6. 最后,您只需实施CsvRecordMapper即可轻松调整新的csv文件格式。 E.g。

    public class UserCsvRecordMapper implements CsvRecordMapper<User> {
    
        public User map(Map<String, String> csvRecord, int lineNumber) {
            String firstname = csvRecord.get("FIRST NAME");
            String lastname = csvRecord.get("LAST NAME");
            String username = csvRecord.get("USERNAME");
            String email = csvRecord.get("EMAIL ADDRESS");
    
            return new User(firstname, lastname, username, email);
        }
    }
    
    public class User {
    
        private String firstname;
        private String lastname;
        private String username;
        private String email;
    
        public User(String firstname, String lastname, String username, String email) {
            this.firstname = firstname;
            this.lastname = lastname;
            this.username = username;
            this.email = email;
        }
    
        public String getFirstname() {
            return firstname;
        }
    
        public String getLastname() {
            return lastname;
        }
    
        public String getUsername() {
            return username;
        }
    
        public String getEmail() {
            return email;
        }
    
        @Override
        public String toString() {
            return "User [firstname=" + firstname + ", lastname=" + lastname + ", username=" + username + ", email=" + email
                    + "]";
        }
    
    }
    
  7. 从客户的角度来看,它很容易使用。

    CSV

    FIRST NAME,LAST NAME,USERNAME,PASSWORD,EMAIL ADDRESS,PHONE NUMBER,PASSPORT,GROUPS,USERCODE,TITLE,ADDRESS 1 ,ADDRESS 2,CITY,STATE,ZIP
    Frank,Riley,friley,changeme,friley@kanab.org,123-456-7890,3,"1,3",1040,Teacher,328 Innovation,Suite # 200 ,state college,PA,16803
    Steve,Brannigan,sbrannigan,changeme,sbrannigan@kanab.org,123-456-7890,3,1,1041,Teacher,328 Innovation,Suite # 200 ,state college,PA,16803
    Marie,Ambrose,mambrose,changeme,mambrose@kanab.org,123-456-7890,3,1,1042,Teacher,328 Innovation,Suite # 200 ,state college,PA,16803
    

    一个简单的主类

    public class Main {
    
        public static void main(String[] args) throws IOException {
            InputStream csvInputStream = Main.class.getResourceAsStream("example.csv");
            InputStreamReader inputStreamReader = new InputStreamReader(csvInputStream);
    
            CsvReader csvReader = new CsvReader();
            List<User> users = csvReader.readAll(inputStreamReader, new UserCsvRecordMapper());
    
            for (User user : users) {
                System.out.println(user);
            }
    
        }
    }
    

    结果是

    User [firstname=Frank, lastname=Riley, username=friley, email=friley@kanab.org]
    User [firstname=Steve, lastname=Brannigan, username=sbrannigan, email=sbrannigan@kanab.org]
    User [firstname=Marie, lastname=Ambrose, username=mambrose, email=mambrose@kanab.org]
    

答案 1 :(得分:0)

这是我非常直截了当的解决方案。

将CSVRecord中的转换器声明为每个所需的实体:

class FirstModel(models.Model):
    first_model_item = models.ForeignKey(Item)
    quantity =  models.IntegerField()

class SecondModel(models.Model):
    second_model_item = models.ForeignKey(FirstModel)
    quantity = models.IntegerField()

然后使Parser更具普遍性

class CreateSecondModelView(generics.CreateAPIView):
    permission_classes = ()
    queryset = SecondModel.objects.all()
    serializer_class = SecondModeSerializer

    def perform_create(self, serializer):
        instance = serializer.save()
        first_model_item = instance.item
        first_model_item.quantity -= instance.quantity
        first_model_item.save()


class UpdateSecondModelView(generics.RetrieveUpdateAPIView):
    permission_classes = ()
    queryset = SecondModel.objects.all()
    serializer_class = SecondModeSerializer

    def perform_update(self, serializer):
        instance = serializer.save()
        #here i want old value of quantity before updating.

然后按以下方式使用

class AliasLocusMapper { 
    public static final String[] FILE_HEADER_MAPPING = {"id", "locusID", "organismid", "variable", "alias"};

    private static final String ID = "id";
    private static final String LOCUS_ID = "locusID";
    private static final String ORGANISM_ID = "organismid";
    private static final String VARIABLE = "variable";
    private static final String ALIAS = "alias";

    public static AliasLocus mapRecord(CSVRecord record) {
        return new AliasLocus(Integer.parseInt(record.get(ID)),
                record.get(LOCUS_ID),
                record.get(ORGANISM_ID),
                record.get(VARIABLE),
                record.get(ALIAS));
    }
}

class ProductMapper { // Product is an example class
    public static final String[] FILE_HEADER_MAPPING = {"id", "title", "price"};

    private static final String ID = "id";
    private static final String TITLE = "title";
    private static final String PRICE = "price";

    public static Product mapRecord(CSVRecord record) {
        return new Product(Integer.parseInt(record.get(ID)),
                record.get(TITLE),
                record.get(PRICE));
    }
}