如何将包含嵌套 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.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.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.Featur

e.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 做更精细控制。