diff --git a/CSV_TO_JSON_README.md b/CSV_TO_JSON_README.md new file mode 100644 index 00000000000..8988bfcf2a8 --- /dev/null +++ b/CSV_TO_JSON_README.md @@ -0,0 +1,240 @@ +# CSV to JSON 工具类 - 重新设计版 + +这是一个完全重新设计的CSV转JSON工具类,解决了之前版本的所有问题,提供了更健壮、更灵活的API。 + +## 主要改进 + +- ✅ **正确的JSON转义**:使用标准JSON转义规则 +- ✅ **完整的CSV解析**:支持RFC 4180标准,包括引号转义 +- ✅ **类型转换可选**:默认不转换类型,通过配置启用 +- ✅ **保持列顺序**:使用LinkedHashMap确保列顺序 +- ✅ **无trim()破坏数据**:保留原始数据格式 +- ✅ **统一的API接口**:提供清晰的Builder模式和静态工厂方法 +- ✅ **完整测试覆盖**:所有功能都有对应的测试 + +## 快速开始 + +### 基本用法 + +```java +import org.apache.commons.lang3.csv.CsvToJson; + +// 从CSV字符串 +String csv = "name,age,city\nJohn,25,New York\nJane,30,Los Angeles"; +String json = CsvToJson.fromString(csv).toJson(); + +// 从CSV文件 +String json = CsvToJson.fromFile(new File("data.csv")).toJson(); + +// 从字节数组 +String json = CsvToJson.fromBytes(csvBytes).toJson(); +``` + +### 使用Builder模式进行配置 + +```java +String csv = "name;age;city\nJohn;25;New York"; +String json = CsvToJson.builder() + .withDelimiter(';') + .withQuoteChar('"') + .withHeaders(true) + .fromString(csv) + .toJson(); +``` + +## 完整API参考 + +### 输入源 + +- `CsvToJson.fromString(String csv)` - 从字符串 +- `CsvToJson.fromFile(File file)` - 从文件(UTF-8) +- `CsvToJson.fromFile(File file, Charset charset)` - 从文件(指定编码) +- `CsvToJson.fromBytes(byte[] bytes)` - 从字节数组(UTF-8) +- `CsvToJson.fromBytes(byte[] bytes, Charset charset)` - 从字节数组(指定编码) +- `CsvToJson.fromReader(Reader reader)` - 从Reader + +### Builder配置选项 + +```java +CsvToJson.builder() + .withDelimiter(',') // 设置分隔符,默认逗号 + .withQuoteChar('"') // 设置引号字符,默认双引号 + .withHeaders(true) // 设置是否有标题行,默认true + .withCustomHeaders("col1", "col2") // 设置自定义标题 + .skipEmptyLines(true) // 是否跳过空行,默认true +``` + +### JSON配置选项 + +```java +CsvToJson.JsonConfig config = new CsvToJson.JsonConfig() + .withTypeConversion(true); // 是否进行类型转换,默认false + +String json = csvToJson.toJson(config); +``` + +## 功能特性 + +### 1. 标准CSV解析 +- 支持RFC 4180标准 +- 正确处理引号转义("") +- 支持包含分隔符的字段 +- 支持多行字段 + +### 2. 数据完整性 +- 保留原始数据格式(不自动trim) +- 正确处理空值和空字符串 +- 保持列顺序 + +### 3. 灵活配置 +- 自定义分隔符 +- 自定义引号字符 +- 可选标题行处理 +- 自定义列名 + +### 4. 类型转换(可选) +- 字符串 → 数字 +- 字符串 → 布尔值 +- 字符串 → null +- 默认禁用,通过配置启用 + +### 5. 错误处理 +- 清晰的异常信息 +- 输入验证 +- 文件存在性检查 + +## 示例 + +### 示例1:基本转换 + +**输入CSV:** +```csv +name,age,city +John Doe,25,New York +Jane Smith,30,Los Angeles +``` + +**代码:** +```java +String csv = "name,age,city\nJohn Doe,25,New York\nJane Smith,30,Los Angeles"; +String json = CsvToJson.fromString(csv).toJson(); +``` + +**输出JSON:** +```json +[{"name":"John Doe","age":"25","city":"New York"},{"name":"Jane Smith","age":"30","city":"Los Angeles"}] +``` + +### 示例2:带引号的CSV + +**输入CSV:** +```csv +name,description,skills +"John Doe","Software Engineer","Java,Python,JavaScript" +"Jane ""The Great"" Smith","Data Scientist","Python,R,SQL" +``` + +**代码:** +```java +String csv = "name,description,skills\n\"John Doe\",\"Software Engineer\",\"Java,Python,JavaScript\"\n\"Jane \"\"The Great\"\" Smith\",\"Data Scientist\",\"Python,R,SQL\""; +String json = CsvToJson.fromString(csv).toJson(); +``` + +### 示例3:自定义分隔符 + +**输入CSV:** +```csv +name;age;city +John;25;New York +Jane;30;Los Angeles +``` + +**代码:** +```java +String csv = "name;age;city\nJohn;25;New York\nJane;30;Los Angeles"; +String json = CsvToJson.builder() + .withDelimiter(';') + .fromString(csv) + .toJson(); +``` + +### 示例4:无标题行(自定义列名) + +**输入CSV:** +```csv +John,25,true +Jane,30,false +Bob,35,true +``` + +**代码:** +```java +String csv = "John,25,true\nJane,30,false\nBob,35,true"; +String json = CsvToJson.builder() + .withHeaders(false) + .withCustomHeaders("name", "age", "active") + .fromString(csv) + .toJson(); +``` + +**输出JSON:** +```json +[{"name":"John","age":"25","active":"true"},{"name":"Jane","age":"30","active":"false"},{"name":"Bob","age":"35","active":"true"}] +``` + +### 示例5:启用类型转换 + +**输入CSV:** +```csv +name,age,salary,active +John,25,75000.50,true +Jane,30,85000,false +``` + +**代码:** +```java +String csv = "name,age,salary,active\nJohn,25,75000.50,true\nJane,30,85000,false"; +String json = CsvToJson.fromString(csv) + .toJson(new CsvToJson.JsonConfig().withTypeConversion(true)); +``` + +**输出JSON:** +```json +[{"name":"John","age":25,"salary":75000.5,"active":true},{"name":"Jane","age":30,"salary":85000,"active":false}] +``` + +## 错误处理 + +所有方法都提供了清晰的错误处理: + +- `NullPointerException` - 当输入为null时 +- `IllegalArgumentException` - 当文件不存在或配置无效时 +- `IOException` - 当发生I/O错误时 + +## 性能考虑 + +- 使用StringBuilder进行高效的JSON构建 +- 流式读取大文件 +- 最小化内存分配 + +## 兼容性 + +- Java 8及以上版本 +- 支持所有标准字符编码 +- 支持Windows、Linux、macOS等所有主流操作系统 + +## 迁移指南 + +从旧版本迁移: + +```java +// 旧版本(已删除) +String json = CsvToJsonUtils.toJsonString(csv); + +// 新版本 +String json = CsvToJson.fromString(csv).toJson(); +``` + +## 许可证 + +Apache License 2.0 - 与Apache Commons Lang项目相同 \ No newline at end of file diff --git a/src/demo/java/CsvToJsonDemo.java b/src/demo/java/CsvToJsonDemo.java new file mode 100644 index 00000000000..04ccf5948da --- /dev/null +++ b/src/demo/java/CsvToJsonDemo.java @@ -0,0 +1,140 @@ +import org.apache.commons.lang3.csv.CsvToJson; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Demo class to showcase the CsvToJson functionality. + */ +public class CsvToJsonDemo { + + public static void main(String[] args) { + try { + System.out.println("=== CSV to JSON Demo ===\n"); + + // Demo 1: Basic CSV string to JSON + System.out.println("1. Basic CSV string to JSON:"); + String csv1 = "name,age,city\nJohn Doe,25,New York\nJane Smith,30,Los Angeles"; + String json1 = CsvToJson.fromString(csv1).toJson(); + System.out.println("CSV Input:"); + System.out.println(csv1); + System.out.println("\nJSON Output:"); + System.out.println(json1); + + // Demo 2: CSV with quoted fields + System.out.println("\n2. CSV with quoted fields:"); + String csv2 = "name,description,skills\n\"John Doe\",\"Software Engineer\",\"Java,Python,JavaScript\"\n\"Jane Smith\",\"Data Scientist\",\"Python,R,SQL\""; + String json2 = CsvToJson.fromString(csv2).toJson(); + System.out.println("CSV Input:"); + System.out.println(csv2); + System.out.println("\nJSON Output:"); + System.out.println(json2); + + // Demo 3: CSV with custom delimiter + System.out.println("\n3. CSV with custom delimiter (semicolon):"); + String csv3 = "name;age;city\nAlice;28;New York\nBob;32;Los Angeles\nCharlie;25;Chicago"; + String json3 = CsvToJson.builder() + .withDelimiter(';') + .fromString(csv3) + .toJson(); + System.out.println("CSV Input:"); + System.out.println(csv3); + System.out.println("\nJSON Output:"); + System.out.println(json3); + + // Demo 4: CSV without headers + System.out.println("\n4. CSV without headers (using custom headers):"); + String csv4 = "John,25,true\nJane,30,false\nBob,35,true"; + String json4 = CsvToJson.builder() + .withHeaders(false) + .withCustomHeaders("employee_name", "years_experience", "is_active") + .fromString(csv4) + .toJson(); + System.out.println("CSV Input:"); + System.out.println(csv4); + System.out.println("\nJSON Output with custom headers:"); + System.out.println(json4); + + // Demo 5: CSV with type conversion + System.out.println("\n5. CSV with automatic type conversion:"); + String csv5 = "name,age,salary,active\nJohn,25,75000.50,true\nJane,30,85000,false"; + String json5 = CsvToJson.fromString(csv5) + .toJson(new CsvToJson.JsonConfig().withTypeConversion(true)); + System.out.println("CSV Input:"); + System.out.println(csv5); + System.out.println("\nJSON Output with type conversion:"); + System.out.println(json5); + + // Demo 6: CSV file to JSON + System.out.println("\n6. CSV file to JSON:"); + File csvFile = new File("demo-data.csv"); + String csvFileData = "product,price,stock\nLaptop,999.99,25\nMouse,19.99,100\nKeyboard,49.99,75"; + Files.write(Paths.get(csvFile.getAbsolutePath()), csvFileData.getBytes(StandardCharsets.UTF_8)); + + String json6 = CsvToJson.fromFile(csvFile).toJson(); + System.out.println("CSV File Content:"); + System.out.println(csvFileData); + System.out.println("\nJSON Output:"); + System.out.println(json6); + + // Demo 7: Handling empty lines and null values + System.out.println("\n7. Handling empty lines and null values:"); + String csv7 = "name,age,city\n\nJohn,25,\n\nJane,,Los Angeles\n"; + String json7 = CsvToJson.fromString(csv7).toJson(); + System.out.println("CSV Input:"); + System.out.println(csv7); + System.out.println("\nJSON Output:"); + System.out.println(json7); + + // Demo 8: Complex CSV with special characters + System.out.println("\n8. Complex CSV with special characters:"); + String csv8 = "name,description,notes\n\"John O'Brien\",\"Line 1\nLine 2\",\"Special chars: \t\n\"\"\n\"Jane \"\"The Great\"\" Smith\",\"Multi-line\nfield\",\"Unicode: 你好\n世界\""; + String json8 = CsvToJson.fromString(csv8).toJson(); + System.out.println("CSV Input:"); + System.out.println(csv8); + System.out.println("\nJSON Output:"); + System.out.println(json8); + + // Demo 9: Builder pattern with full configuration + System.out.println("\n9. Builder pattern with full configuration:"); + String csv9 = "name|age|salary|active\n\"John Doe\"|25|75000.50|true\n\"Jane Smith\"|30|85000.00|false"; + String json9 = CsvToJson.builder() + .withDelimiter('|') + .withQuoteChar('"') + .withHeaders(true) + .skipEmptyLines(true) + .fromString(csv9) + .toJson(new CsvToJson.JsonConfig().withTypeConversion(true)); + System.out.println("CSV Input:"); + System.out.println(csv9); + System.out.println("\nJSON Output:"); + System.out.println(json9); + + // Demo 10: Error handling + System.out.println("\n10. Error handling examples:"); + try { + CsvToJson.fromFile(new File("nonexistent.csv")).toJson(); + } catch (IllegalArgumentException e) { + System.out.println("Expected error for nonexistent file: " + e.getMessage()); + } + + try { + CsvToJson.builder() + .withHeaders(false) + .fromString("John,25") + .toJson(); + } catch (IllegalArgumentException e) { + System.out.println("Expected error for missing headers: " + e.getMessage()); + } + + System.out.println("\n=== All demos completed successfully! ==="); + + } catch (IOException e) { + System.err.println("Error during demo: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/commons/lang3/csv/CsvToJson.java b/src/main/java/org/apache/commons/lang3/csv/CsvToJson.java new file mode 100644 index 00000000000..de8d458fc23 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/csv/CsvToJson.java @@ -0,0 +1,544 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.csv; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Utility class for converting CSV data to JSON format. + * + *
This class provides a simple and efficient way to convert CSV data from various sources + * to JSON format while maintaining the original order of columns and providing flexible + * configuration options.
+ * + *Features:
+ *Usage example:
+ *{@code
+ * String csv = "name,age,city\nJohn,25,New York\nJane,30,Los Angeles";
+ * String json = CsvToJson.fromString(csv).toJson();
+ * }
+ *
+ * @since 3.20.0
+ */
+public final class CsvToJson {
+
+ private final Reader reader;
+ private final CsvConfig config;
+
+ private CsvToJson(Reader reader, CsvConfig config) {
+ this.reader = Objects.requireNonNull(reader, "reader");
+ this.config = Objects.requireNonNull(config, "config");
+ }
+
+ /**
+ * Creates a new CsvToJson instance from a file.
+ *
+ * @param file the CSV file to read from
+ * @return a new CsvToJson instance
+ * @throws NullPointerException if file is null
+ * @throws IllegalArgumentException if file does not exist
+ */
+ public static CsvToJson fromFile(File file) {
+ return fromFile(file, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Creates a new CsvToJson instance from a file with specified charset.
+ *
+ * @param file the CSV file to read from
+ * @param charset the charset to use
+ * @return a new CsvToJson instance
+ * @throws NullPointerException if file or charset is null
+ * @throws IllegalArgumentException if file does not exist
+ */
+ public static CsvToJson fromFile(File file, Charset charset) {
+ Objects.requireNonNull(file, "file");
+ Objects.requireNonNull(charset, "charset");
+
+ if (!file.exists()) {
+ throw new IllegalArgumentException("File does not exist: " + file.getAbsolutePath());
+ }
+
+ try {
+ return new CsvToJson(new InputStreamReader(new FileInputStream(file), charset), new CsvConfig());
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to read file: " + file.getAbsolutePath(), e);
+ }
+ }
+
+ /**
+ * Creates a new CsvToJson instance from a string.
+ *
+ * @param csv the CSV data as a string
+ * @return a new CsvToJson instance
+ * @throws NullPointerException if csv is null
+ */
+ public static CsvToJson fromString(String csv) {
+ Objects.requireNonNull(csv, "csv");
+ return new CsvToJson(new StringReader(csv), new CsvConfig());
+ }
+
+ /**
+ * Creates a new CsvToJson instance from a byte array.
+ *
+ * @param bytes the CSV data as a byte array
+ * @return a new CsvToJson instance
+ * @throws NullPointerException if bytes is null
+ */
+ public static CsvToJson fromBytes(byte[] bytes) {
+ return fromBytes(bytes, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Creates a new CsvToJson instance from a byte array with specified charset.
+ *
+ * @param bytes the CSV data as a byte array
+ * @param charset the charset to use
+ * @return a new CsvToJson instance
+ * @throws NullPointerException if bytes or charset is null
+ */
+ public static CsvToJson fromBytes(byte[] bytes, Charset charset) {
+ Objects.requireNonNull(bytes, "bytes");
+ Objects.requireNonNull(charset, "charset");
+ return new CsvToJson(new InputStreamReader(new java.io.ByteArrayInputStream(bytes), charset), new CsvConfig());
+ }
+
+ /**
+ * Creates a new CsvToJson instance from a reader.
+ *
+ * @param reader the reader to read CSV data from
+ * @return a new CsvToJson instance
+ * @throws NullPointerException if reader is null
+ */
+ public static CsvToJson fromReader(Reader reader) {
+ Objects.requireNonNull(reader, "reader");
+ return new CsvToJson(reader, new CsvConfig());
+ }
+
+ /**
+ * Returns a builder to configure the CSV to JSON conversion.
+ *
+ * @return a builder instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Converts the CSV data to JSON format.
+ *
+ * @return the JSON representation of the CSV data
+ * @throws IOException if an I/O error occurs
+ */
+ public String toJson() throws IOException {
+ return toJson(new JsonConfig());
+ }
+
+ /**
+ * Converts the CSV data to JSON format with specified configuration.
+ *
+ * @param jsonConfig the JSON configuration
+ * @return the JSON representation of the CSV data
+ * @throws IOException if an I/O error occurs
+ */
+ public String toJson(JsonConfig jsonConfig) throws IOException {
+ Objects.requireNonNull(jsonConfig, "jsonConfig");
+
+ List