JSON转字符串存库及反向转换的优化方案咨询
问题需求
需将如下结构的JSON转换为字符串字段storedInput存入数据库,同时支持把该字符串反向还原为原JSON格式:
{ "students":[{ "name": "Albert", "code":"GE", "marks":[{"mark":"20"},{"mark":"40"}]}, { "name": "Gert", "code":"LE", "marks":[{"mark":"26"}]}, { "name": "John" }, { "name": "John Doe", "code":"LP", "marks":[{"mark":"40"}]} ] }
原方案采用分隔符拼接字符串(示例:"Albert-GE-20&40#Gert-LE-26#John-$-$#John Doe-LP-40"),但反向解析时极易出错,对应的Java转换代码如下:
public String convertStudentList(List<Student> studentList) { return studentList.stream().map(this::mapStudent).collect(Collectors.joining("#")); } public String checkData(String data) { return Optional.ofNullable(data).isPresent() ? data : "$"; } public String mapStudent(Student student) { List<Marks> marks = student.getMarks(); if (marks != null) { String mark = marks.stream().map(m -> m.getMark()).collect(Collectors.joining("&")); return checkData(student.getName()) + "-" + checkData(student.getCode()) + "-" + mark; } else { return checkData(student.getName()) + "-" + checkData(student.getCode()) + "-" + "$"; } }
约束条件
- 无法修改数据库表结构
- 受存储空间限制,不能直接存储完整JSON(如用Jackson ObjectMapper序列化的结果),必须仅存储有效数据且能完整还原
可行解决方案
针对原方案反解析困难的问题,推荐带转义的分隔符方案,核心是对包含分隔符的字段内容做转义处理,同时明确字段拆分规则,确保反向解析时能准确还原数据。
核心设计规则
定义分隔符与转义符:
- 学生条目之间用
#分隔 - 单个学生的
name、code、marks组之间用|分隔(避免和姓名/成绩中的-冲突) - 同一学生的多个成绩用
&分隔 - 转义符用
\:如果字段内容中出现#、|、&、\,则在字符前添加\做转义
- 学生条目之间用
序列化(转字符串)逻辑:
- 遍历每个学生,对
name、code做转义处理 - 若
marks非空,将所有mark值转义后用&拼接;为空则用$占位 - 用
|拼接转义后的name、code、成绩字符串 - 用
#拼接所有学生的字符串
- 遍历每个学生,对
反序列化(还原JSON)逻辑:
- 拆分字符串时跳过转义后的特殊字符(如
\#不视为学生分隔符) - 拆分出的字段内容再做反转义,还原原始内容
- 根据占位符
$判断是否为空值,进而还原对应的对象结构
- 拆分字符串时跳过转义后的特殊字符(如
示例转换结果
原JSON对应的序列化字符串:
Albert|GE|20&40#Gert|LE|26#John|$|$#John Doe|LP|40
若有学生姓名为John#Doe,转义后会变为John\#Doe,避免拆分时误判为学生分隔符。
Java代码实现示例
序列化工具方法
private static final String STUDENT_SEP = "#"; private static final String FIELD_SEP = "|"; private static final String MARK_SEP = "&"; private static final String ESCAPE = "\\"; private static final String NULL_PLACEHOLDER = "$"; private String escape(String input) { if (input == null) return NULL_PLACEHOLDER; return input.replace(ESCAPE, ESCAPE + ESCAPE) .replace(STUDENT_SEP, ESCAPE + STUDENT_SEP) .replace(FIELD_SEP, ESCAPE + FIELD_SEP) .replace(MARK_SEP, ESCAPE + MARK_SEP); } public String convertStudentList(List<Student> studentList) { return studentList.stream().map(this::mapStudent).collect(Collectors.joining(STUDENT_SEP)); } public String mapStudent(Student student) { String escapedName = escape(student.getName()); String escapedCode = escape(student.getCode()); String marksStr; if (student.getMarks() != null && !student.getMarks().isEmpty()) { marksStr = student.getMarks().stream() .map(m -> escape(m.getMark())) .collect(Collectors.joining(MARK_SEP)); } else { marksStr = NULL_PLACEHOLDER; } return String.join(FIELD_SEP, escapedName, escapedCode, marksStr); }
反序列化工具方法
private String unescape(String input) { if (NULL_PLACEHOLDER.equals(input)) return null; StringBuilder sb = new StringBuilder(); boolean escaped = false; for (char c : input.toCharArray()) { if (escaped) { sb.append(c); escaped = false; } else if (ESCAPE.charAt(0) == c) { escaped = true; } else { sb.append(c); } } return sb.toString(); } public List<Student> parseStudentList(String storedInput) { if (storedInput == null || storedInput.isEmpty()) return Collections.emptyList(); List<Student> students = new ArrayList<>(); // 手动拆分学生条目,处理转义的# List<String> studentStrs = new ArrayList<>(); StringBuilder current = new StringBuilder(); boolean escaped = false; for (char c : storedInput.toCharArray()) { if (escaped) { current.append(c); escaped = false; } else if (ESCAPE.charAt(0) == c) { escaped = true; } else if (STUDENT_SEP.charAt(0) == c) { studentStrs.add(current.toString()); current.setLength(0); } else { current.append(c); } } if (current.length() > 0) { studentStrs.add(current.toString()); } for (String studentStr : studentStrs) { // 拆分学生字段,处理转义的| List<String> fields = new ArrayList<>(); current.setLength(0); escaped = false; for (char c : studentStr.toCharArray()) { if (escaped) { current.append(c); escaped = false; } else if (ESCAPE.charAt(0) == c) { escaped = true; } else if (FIELD_SEP.charAt(0) == c) { fields.add(current.toString()); current.setLength(0); } else { current.append(c); } } if (current.length() > 0) { fields.add(current.toString()); } Student student = new Student(); student.setName(unescape(fields.get(0))); student.setCode(unescape(fields.get(1))); String marksField = fields.get(2); if (!NULL_PLACEHOLDER.equals(marksField)) { List<Marks> marks = new ArrayList<>(); // 拆分成绩,处理转义的& List<String> markStrs = new ArrayList<>(); current.setLength(0); escaped = false; for (char c : marksField.toCharArray()) { if (escaped) { current.append(c); escaped = false; } else if (ESCAPE.charAt(0) == c) { escaped = true; } else if (MARK_SEP.charAt(0) == c) { markStrs.add(current.toString()); current.setLength(0); } else { current.append(c); } } if (current.length() > 0) { markStrs.add(current.toString()); } for (String markStr : markStrs) { Marks mark = new Marks(); mark.setMark(unescape(markStr)); marks.add(mark); } student.setMarks(marks); } else { student.setMarks(null); } students.add(student); } return students; }
方案优势
- 解决了原方案中字段含分隔符导致的解析错误问题,反向还原逻辑可靠
- 仅存储有效数据,空间占用和原方案相当,满足存储限制
- 无需修改数据库表结构,直接替换原有转换逻辑即可落地
内容的提问来源于stack exchange,提问作者kselvan9000
相关产品推荐
相关产品推荐

