17370845950

如何将包含嵌套 JSON 字符串的 CSV 正确解析为结构化 JSON

本文介绍一种绕过 csv 格式缺陷的实用方案:当 csv 中某列值本身是未加引号的 json 字符串(如 {"name":"john","age":30})时,通过预处理替换边界符号并自定义引号字符,使 jackson 能安全解析嵌套 json 为字符串字段,最终输出符合预期的 json 结构。

CSV 规范明确要求:若字段值包含分隔符(如逗号)、换行符或双引号,必须用引号(默认为 ")包裹。但现实中,部分系统导出的 CSV 会直接将 JSON 字符串作为纯文本写入某列,且未加引号——这会导致标准 CSV 解析器误判字段边界,造成解析失败或数据错位。

例如以下非法 CSV:

header1,header2,header3
value1,value2,{"name":"John","age":30,"car":null}

第三列的 JSON 含逗号,却无引号包裹,CsvMapper 默认会将其截断为 {"name":"John" 和 "age":30,"car":null} 两列,破坏结构。

✅ 解决思路:临时“伪引号”预处理

由于无法修改上游生成逻辑,我们采用轻量级文本预处理 + 自定义引号字符的方式:

  • 将每个 JSON 字段的最外层 { 替换为 |{,} 替换为 }|(注意:仅处理首尾大括号,避免影响 JSON 内部嵌套结构);
  • 配置 CsvSchema 使用 | 作为 quote character;
  • Jackson 即可将 |{...}| 识别为一个完整带引号的字段,并原样保留为字符串(而非尝试解析 JSON)。
⚠️ 注意:该方法适用于 JSON 字段不嵌套在更复杂结构中(如 CSV 行内含多个 JSON 列),且 JSON 本身不含 | 字符的场景。若存在 |,需改用更安全的分隔符(如 \u0001)或正则精准匹配首尾大括号。

示例代码(Jackson 2.15+)

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.j

son.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.stream.Collectors; public class CsvToJsonConverter { public static void main(String[] args) throws Exception { File csvFile = new File("./resource/test.csv").getAbsoluteFile(); String csv = Files.readAllLines(csvFile.toPath()) .stream() .collect(Collectors.joining(System.lineSeparator())); // ✅ 关键预处理:仅包裹最外层 JSON 的 {},避免破坏内部结构 csv = csv.replaceFirst("(?<=,|^)\\{", "|{") .replaceAll("}\\s*(?=,|$)", "}|"); CsvMapper csvMapper = CsvMapper.builder().build(); CsvSchema schema = CsvSchema.builder() .setQuoteChar('|') .setUseHeader(true) .build(); JsonNode jsonNode = csvMapper.readerFor(JsonNode.class) .with(schema) .readValue(csv); // 输出格式化 JSON(含缩进) JsonMapper jsonMapper = JsonMapper.builder() .enable(JsonMapper.Feature.INDENT_OUTPUT) .build(); jsonMapper.writeValue(System.out, jsonNode); } }

输出结果

{
  "header1" : "value1",
  "header2" : "value2",
  "header3" : "{\"name\":\"John\",\"age\":30,\"car\":null}"
}

可见 header3 的值被正确保留为转义后的 JSON 字符串,完全符合需求。

总结

  • 根本原因:非法 CSV 格式导致解析歧义;
  • 核心技巧:用可控符号(|)临时“标记”JSON 边界,借力 CSV 引号机制实现字段隔离;
  • 适用边界:适合单层 JSON 字段、无冲突字符的场景;生产环境建议推动上游修复 CSV 导出逻辑(添加标准双引号包裹);
  • 扩展建议:对复杂场景,可结合 JsonParser 手动流式解析,或使用 OpenCSV + 自定义 CellProcessor 做更精细控制。