本文介绍一种实用方法,解决 csv 中某列值本身为 json 字符串(含逗号、花括号等特殊字符)导致解析失败的问题,通过预处理+自定义引号字符的方式,借助 jackson 安全地完成 csv 到嵌套 json 的转换。
在实际数据集成场景中,常遇到 CSV 文件某列(如 header3)直接存储了 JSON 格式的字符串(例如 {"name":"John","age":30,"car":null})。标准 CSV 解析器会将其中的逗号、花括号或引号误判为分隔符或结构标记,导致解析错误或字段截断。虽然规范要求此类值应被双引号包裹(如 "{"name":"John"}"),但若上游系统无法修正输出(即 CSV 本身“不合规”),我们就需在解析前进行智能预处理。
核心思路是:临时替换 JSON 边界符号,使其能被 CSV 解析器识别为受保护的字段内容。由于原始 CSV 未使用引号,我们选择用 | 作为临时引号字符,并仅对最外层 { 和 } 进行配对替换(避免破坏 JSON 内部结构):
这样,{"name":"John","age":30,"car":null} 变为 |{"name":"John","age":30,"car":null}|,CSV 解析器即可将其整体视为一个带引号的字段值。
以下是完整可运行示例(基于 Jackson 2.15+):
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; import java.io.File; import java.nio.file.Files; import java.util.List; import java.util.stream.Collectors; public class CsvApp { public static void main(String[] args) throws Exception { File csvFile = new File("./resource/test.csv").getAbsoluteFile(); List
lines = Files.readAllLines(csvFile.toPath()); // 预处理:仅替换每行首尾的 { 和 },避免影响内部 JSON String processedCsv = lines.stream() .map(line -> { int firstBrace = line.indexOf('{'); int lastBrace = line.lastIndexOf('}'); if (firstBrace != -1 && lastBrace != -1 && firstBrace < lastBrace) { return line.substring(0, firstBrace) + "|{" + line.substring(firstBrace + 1, lastBrace) + "}|" + line.substring(lastBrace + 1); } return line; // 无 JSON 结构则保持原样 }) .collect(Collectors.joining(System.lineSeparator())); // 构建 CSV 解析器:指定 | 为引号字符,启用表头 CsvMapper csvMapper = new CsvMapper(); CsvSchema schema = CsvSchema.builder() .setQuoteChar('|') .setUseHeader(true) .build(); // 解析为 JsonNode(自动映射为 Map-like 结构) JsonNode csvContent = csvMapper.readerFor(JsonNode.class) .with(schema) .readValue(processedCsv); // 输出格式化 JSON(含缩进) JsonMapper jsonMapper = JsonMapper.builder() .enable(SerializationFeature.INDENT_OUTPUT) .build(); jsonMapper.writeValue(System.out, csvContent); } }
✅ 关键注意事项:
最终输出符合预期——header3 的值被保留为转义后的 JSON 字符串(即 "\"{\\\"name\\\":\\\"John\\\",\\\"age\\\":30,\\\"car\\\":null}\""),可在后续步骤中按需调用 JsonMapper.readTree() 二次解析为嵌套对象。