diff --git a/CHANGELOG.md b/CHANGELOG.md index e6ea0f424..32e5d2c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * 【core 】 增加PartitionIter(pr#402@Gitee) * 【all 】 增加异常爬栈开关(pr#403@Gitee) * 【core 】 优化Combination中C(n,n)的逻辑(pr#1792@Github) +* 【core 】 Csv读写支持别名(issue#1791@Github) ### 🐞Bug修复 * 【core 】 修复MapUtil.sort比较器不一致返回原map的问题(issue#I46AQJ@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvConfig.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvConfig.java index 7f3584975..0f0e5a1b9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvConfig.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvConfig.java @@ -3,6 +3,8 @@ package cn.hutool.core.text.csv; import cn.hutool.core.util.CharUtil; import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; /** * CSV基础配置项,此配置项可用于读取和写出CSV,定义了包括字段分隔符、文本包装符等符号 @@ -11,6 +13,7 @@ import java.io.Serializable; * @author looly * @since 4.0.5 */ +@SuppressWarnings("unchecked") public class CsvConfig> implements Serializable { private static final long serialVersionUID = -8069578249066158459L; @@ -26,6 +29,10 @@ public class CsvConfig> implements Serializable { * 注释符号,用于区分注释行,默认'#' */ protected char commentCharacter = '#'; + /** + * 标题别名 + */ + protected Map headerAlias = new LinkedHashMap<>(); /** * 设置字段分隔符,默认逗号',' @@ -33,7 +40,6 @@ public class CsvConfig> implements Serializable { * @param fieldSeparator 字段分隔符,默认逗号',' * @return this */ - @SuppressWarnings("unchecked") public T setFieldSeparator(final char fieldSeparator) { this.fieldSeparator = fieldSeparator; return (T) this; @@ -45,7 +51,6 @@ public class CsvConfig> implements Serializable { * @param textDelimiter 文本分隔符,文本包装符,默认双引号'"' * @return this */ - @SuppressWarnings("unchecked") public T setTextDelimiter(char textDelimiter) { this.textDelimiter = textDelimiter; return (T) this; @@ -58,9 +63,45 @@ public class CsvConfig> implements Serializable { * @return this * @since 5.5.7 */ - @SuppressWarnings("unchecked") public T setCommentCharacter(char commentCharacter) { this.commentCharacter = commentCharacter; return (T) this; } + + /** + * 设置标题行的别名Map + * + * @param headerAlias 别名Map + * @return this + * @since 5.7.10 + */ + public T setHeaderAlias(Map headerAlias) { + this.headerAlias = headerAlias; + return (T) this; + } + + /** + * 增加标题别名 + * + * @param header 标题 + * @param alias 别名 + * @return this + * @since 5.7.10 + */ + public T addHeaderAlias(String header, String alias) { + this.headerAlias.put(header, alias); + return (T) this; + } + + /** + * 去除标题别名 + * + * @param header 标题 + * @return this + * @since 5.7.10 + */ + public T removeHeaderAlias(String header) { + this.headerAlias.remove(header); + return (T) this; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java index 43437874c..7f2cba6ab 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java @@ -2,6 +2,7 @@ package cn.hutool.core.text.csv; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; @@ -165,7 +166,11 @@ public final class CsvParser implements Closeable, Serializable { private void initHeader(final List currentFields) { final Map localHeaderMap = new LinkedHashMap<>(currentFields.size()); for (int i = 0; i < currentFields.size(); i++) { - final String field = currentFields.get(i); + String field = currentFields.get(i); + if (MapUtil.isNotEmpty(this.config.headerAlias)) { + // 自定义别名 + field = ObjectUtil.defaultIfNull(this.config.headerAlias.get(field), field); + } if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) { localHeaderMap.put(field, i); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java index 868df3034..fcc37f54a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java @@ -30,7 +30,7 @@ public final class CsvRow implements List { * @param headerMap 标题Map * @param fields 数据列表 */ - public CsvRow(final long originalLineNumber, final Map headerMap, final List fields) { + public CsvRow(long originalLineNumber, Map headerMap, List fields) { Assert.notNull(fields, "fields must be not null!"); this.originalLineNumber = originalLineNumber; this.headerMap = headerMap; @@ -53,10 +53,8 @@ public final class CsvRow implements List { * @return 字段值,null表示无此字段值 * @throws IllegalStateException CSV文件无标题行抛出此异常 */ - public String getByName(final String name) { - if (headerMap == null) { - throw new IllegalStateException("No header available"); - } + public String getByName(String name) { + Assert.notNull(this.headerMap, "No header available!"); final Integer col = headerMap.get(name); if (col != null) { diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java index 24edba363..5dee35f74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java @@ -6,6 +6,7 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -20,6 +21,7 @@ import java.io.Serializable; import java.io.Writer; import java.nio.charset.Charset; import java.util.Collection; +import java.util.List; import java.util.Map; /** @@ -217,8 +219,9 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { public CsvWriter write(CsvData csvData) { if (csvData != null) { // 1、写header - if (CollUtil.isNotEmpty(csvData.getHeader())) { - this.writeLine(csvData.getHeader().toArray(new String[0])); + final List header = csvData.getHeader(); + if (CollUtil.isNotEmpty(header)) { + this.writeHeaderLine(header.toArray(new String[0])); } // 2、写内容 this.write(csvData.getRows()); @@ -239,8 +242,8 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { Map map; for (Object bean : beans) { map = BeanUtil.beanToMap(bean); - if(isFirst){ - writeLine(map.keySet().toArray(new String[0])); + if (isFirst) { + writeHeaderLine(map.keySet().toArray(new String[0])); isFirst = false; } writeLine(Convert.toStrArray(map.values())); @@ -250,6 +253,29 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { return this; } + /** + * 写出一行头部行,支持标题别名 + * + * @param fields 字段列表 ({@code null} 值会被做为空值追加 + * @return this + * @throws IORuntimeException IO异常 + * @since 5.7.10 + */ + public CsvWriter writeHeaderLine(String... fields) throws IORuntimeException { + final Map headerAlias = this.config.headerAlias; + if (MapUtil.isNotEmpty(headerAlias)) { + // 标题别名替换 + String alias; + for (int i = 0; i < fields.length; i++) { + alias = headerAlias.get(fields[i]); + if (null != alias) { + fields[i] = alias; + } + } + } + return writeLine(fields); + } + /** * 写出一行 * diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java index 041b3a8ba..908f1c5c5 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java @@ -47,6 +47,31 @@ public class CsvReaderTest { Assert.assertEquals("22", result.get(2).get("age")); } + @Test + public void readAliasMapListTest(){ + final CsvReadConfig csvReadConfig = CsvReadConfig.defaultConfig(); + csvReadConfig.addHeaderAlias("姓名", "name"); + + final CsvReader reader = CsvUtil.getReader(csvReadConfig); + final List> result = reader.readMapList( + ResourceUtil.getUtf8Reader("test_bean.csv")); + + Assert.assertEquals("张三", result.get(0).get("name")); + Assert.assertEquals("男", result.get(0).get("gender")); + Assert.assertEquals("无", result.get(0).get("focus")); + Assert.assertEquals("33", result.get(0).get("age")); + + Assert.assertEquals("李四", result.get(1).get("name")); + Assert.assertEquals("男", result.get(1).get("gender")); + Assert.assertEquals("好对象", result.get(1).get("focus")); + Assert.assertEquals("23", result.get(1).get("age")); + + Assert.assertEquals("王妹妹", result.get(2).get("name")); + Assert.assertEquals("女", result.get(2).get("gender")); + Assert.assertEquals("特别关注", result.get(2).get("focus")); + Assert.assertEquals("22", result.get(2).get("age")); + } + @Test public void readBeanListTest(){ final CsvReader reader = CsvUtil.getReader(); diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java new file mode 100644 index 000000000..18203196c --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java @@ -0,0 +1,24 @@ +package cn.hutool.core.text.csv; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import org.junit.Ignore; +import org.junit.Test; + +public class CsvWriterTest { + + @Test + @Ignore + public void writeWithAliasTest(){ + final CsvWriteConfig csvWriteConfig = CsvWriteConfig.defaultConfig() + .addHeaderAlias("name", "姓名") + .addHeaderAlias("gender", "性别"); + + final CsvWriter writer = CsvUtil.getWriter( + FileUtil.file("d:/test/csvAliasTest.csv"), + CharsetUtil.CHARSET_GBK, false, csvWriteConfig); + + writer.writeHeaderLine("name", "gender", "address"); + writer.close(); + } +}