1. 这篇文章真正要解决的问题如果你正在做一个“SpringBoot Vue Uniapp LangChain4j SpringAI”的智能视频推荐小程序前期大概率把精力都放在了模型接入、推荐算法、前后端联调上。但项目一旦进入中后期你会发现一个很尴尬的问题系统里到处都在手工拼接 JSON。举几个真实场景前端 Uniapp 请求后端接口后端返回的结果需要统一包装成{code, message, data}结构。调用 SpringAI 或 LangChain4j 时大模型返回的内容是文本但你可能希望它输出结构化 JSON比如推荐理由、视频标签、用户兴趣画像。第三方接口返回的 JSON 字段名是snake_case而你的 Java 实体类字段是camelCase不做处理就只能写一堆getString(video_id)。日志里要打印请求参数结果一个不小心把密码字段也序列化出去了。这些问题的本质是JSON 在 Java 后端里不只是“数据格式”它是系统对外交互的契约是前后端协作的边界也是大模型输出落地的关键环节。如果 JSON 工具类写得不好项目越往后维护成本越高。这篇文章不讲空泛的“JSON 是什么”而是基于智能视频推荐小程序这个具体项目把 JSON 工具类的设计思路、代码实现、坑点排查讲透。读完你可以直接把这套工具类搬进自己的项目里或者根据业务场景改造成适合自己的版本。2. JSON 工具类的基础概念与核心设计思路2.1 为什么 Java 项目需要自定义 JSON 工具类很多初学者会问项目里已经引入了 Fastjson 或 Jackson直接调用不就行了为什么还要自己封装一层直接调用确实可以但会带来三个问题第一调用方式不统一。团队里有人用JSONObject.parseObject有人用ObjectMapper.readValue还有人用Gson.fromJson。一旦依赖升级或需要全局调整序列化策略改动会散落到几十个文件里。第二异常处理不统一。JSON.parseObject遇到格式错误会抛异常但不同调用者处理异常的方式完全不同有人捕获后记录日志有人直接忽略有人抛给上层。最终表现是同样的错误在系统里出现多种行为。第三安全策略难统一。比如某些字段序列化时不能输出日期格式要统一成yyyy-MM-dd HH:mm:ss空值要不要输出这些配置如果散落各处很难保证一致。所以自定义 JSON 工具类的核心价值不是“重复造轮子”而是统一门面 兜底策略 安全控制。你可以把它理解成公司的前台来访者不需要知道每个部门在哪只要到前台前台会统一接待、登记、引导。2.2 主流 JSON 库选型对比智能视频推荐小程序的后端是 SpringBoot 3.x默认自带 Jackson。但在国内团队里Fastjson2 和 Hutool 的 JSON 工具也很常见。下面用一个简单对比说明特性JacksonFastjson2Hutool JSON是否被 Spring Boot 默认集成是否否性能良好优秀中等API 易用性偏底层需配置简单直接简单直接对 Java 8 日期时间支持需注册模块内置支持较好支持社区活跃度高高较高适合场景作为基础框架默认实现追求性能简化代码不想额外引入复杂依赖从项目稳定性角度我个人更推荐以 Jackson 为基础封装。原因很简单SpringBoot 全家桶已经依赖 Jackson你不需要额外引入一个可能引发依赖冲突的大库。但如果你的团队对 Fastjson2 已经很熟也不排斥那用它封装同样可行。本文示例以Jackson为主但封装的思路完全适用于其他库。2.3 JSON 工具类的边界与职责封装工具类之前先划清边界。工具类只负责以下几件事Java 对象转 JSON 字符串。JSON 字符串转 Java 对象。JSON 字符串转JsonNode/JSONObject之类的通用结构。复杂类型转换比如ListVideoInfo。统一处理日期格式、空值策略、异常包装。工具类不应该做的事不负责业务校验比如视频 ID 是否存在。不负责日志埋点那是日志框架的事。不负责网络传输那是 Controller 和 Feign 的事。边界清晰之后工具类的代码才不会越写越臃肿。3. 环境准备与项目依赖配置3.1 项目环境说明本文示例基于以下环境版本请以你实际项目为准JDK 17 或以上。Spring Boot 3.x自动包含 Jackson 2.x。Maven 或 Gradle 作为构建工具。后端工程名示例smart-video-server。如果你的项目是 Spring Boot 2.x依赖坐标略有不同但 Jackson 的核心 API 一致示例代码可以无缝迁移。3.2 添加依赖如果使用 Spring Boot请检查pom.xml中已经存在spring-boot-starter-web它会自动引入 Jacksondependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency如果你需要更精细地控制 Jackson 特性可以显式添加 Jackson 依赖dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency dependency groupIdcom.fasterxml.jackson.datatype/groupId artifactIdjackson-datatype-jsr310/artifactId /dependencyjackson-datatype-jsr310是 Java 8 日期时间类型LocalDate、LocalDateTime序列化的关键依赖。如果你的项目里没有它LocalDateTime默认会被序列化成数组而不是字符串。3.3 配置文件中的 JSON 相关参数在application.yml里我们可以统一配置 Jackson 的全局行为spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 default-property-inclusion: non_null serialization: fail-on-empty-beans: false deserialization: fail-on-unknown-properties: false这些配置的含义date-format统一日期格式。time-zone指定时区避免服务器和数据库时区不一致。default-property-inclusion: non_null序列化时忽略null字段减小 JSON 体积。fail-on-unknown-properties: false反序列化时忽略未知字段。这个配置很有用因为前端或第三方接口经常多传字段如果设置为 true会导致反序列化直接失败。不过要注意spring.jackson.date-format对LocalDateTime并不总是生效。如果发现日期格式没变需要在工具类里通过Jackson2ObjectMapperBuilder或ObjectMapper手动注册JavaTimeModule并设置LocalDateTime的序列化格式。这一点在后面的工具类代码中会体现。4. 核心流程拆解从 JSON 字符串到 Java 对象4.1 整体流转在实际项目中一次典型的 JSON 处理流程是这样的前端 Uniapp 调用后端接口传入 JSON 字符串。SpringMVC 通过 HttpMessageConverter 将 JSON 自动反序列化为 Controller 方法参数对象。Controller 调用 Service 层处理业务。Service 层调用 LangChain4j 或 SpringAI 与大模型交互大模型返回文本或 JSON。Service 层使用 JSON 工具类将大模型返回内容解析成结构化对象。Controller 将返回对象交给 SpringMVC 序列化为 JSON 响应给前端。可以看到第 2 步和第 6 步由框架完成但第 5 步必须自己处理。这就是 JSON 工具类的主要使用场景。4.2 实体类设计示例假设我们的智能视频推荐小程序里有这样一个视频信息实体// 文件路径src/main/java/com/example/smartvideo/model/VideoInfo.java package com.example.smartvideo.model; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; Data public class VideoInfo { private Long id; private String title; private String coverUrl; private BigDecimal score; private ListString tags; JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime createTime; /** * 内部字段序列化时忽略避免泄露给前端 */ JsonIgnore private String internalRemark; }这里用到了三个注解JsonFormat指定LocalDateTime的格式。JsonIgnore序列化时忽略该字段适合内部使用、不想暴露给前端的字段。JsonProperty后面会用到显式指定 JSON 字段名解决命名不一致问题。4.3 手工拼接 JSON 的错误示范新手最容易写出这样的代码String json {\code\:200,\message\:\success\,\data\:{\title\:\ title \}};这种写法至少有四个风险第一title里如果包含双引号、反斜杠、换行符拼接出来的 JSON 直接非法。第二如果title为null最终 JSON 会变成{title:null}但如果你拼成title: null 就会生成title:null虽然勉强合法但连字符串的引号都不见了属于碰巧能用。第三一旦结构嵌套变深手工拼接简直是一场灾难。第四没有转义处理很容易引入 JSON 注入或 XSS 风险。切记能用工具类完成 JSON 序列化的地方永远不要手工拼接。4.4 大模型输出 JSON 的特殊处理在智能视频推荐小程序里大模型返回的往往不是严格 JSON。常见情况是前后有解释性文字比如以下是推荐结果{...}。返回 Markdown 代码块包裹的 JSON。字段名和实体类不一致。偶尔多出逗号或注释。因此解析大模型输出时要先做一次清洗提取出 JSON 片段再解析。具体实现会在工具类中提供。5. 完整示例JSON 工具类代码实现5.1 工具类整体结构这里以一个基于 Jackson 的JsonUtils为例。它既能处理普通 Java 对象转换也能处理大模型非标准 JSON 输出的提取。// 文件路径src/main/java/com/example/smartvideo/util/JsonUtils.java package com.example.smartvideo.util; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import org.springframework.util.StringUtils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; public class JsonUtils { private static final DateTimeFormatter DATE_TIME_FORMATTER DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); private static final ObjectMapper OBJECT_MAPPER createObjectMapper(); private JsonUtils() { } private static ObjectMapper createObjectMapper() { ObjectMapper objectMapper new ObjectMapper(); // 注册 Java 8 时间模块 JavaTimeModule javaTimeModule new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)); objectMapper.registerModule(javaTimeModule); // 序列化时忽略 null 字段 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 反序列化时忽略未知字段 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 允许单个字符串作为 JSON 值传入 objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); // 空对象不报错 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); return objectMapper; } }这里的关键点是手动注册JavaTimeModule因为仅仅在application.yml中配置spring.jackson.date-format不一定能覆盖LocalDateTime。5.2 核心 API对象转 JSON下面添加序列化相关方法// 文件路径src/main/java/com/example/smartvideo/util/JsonUtils.java接上文 public static String toJson(Object obj) { if (obj null) { return null; } try { return OBJECT_MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { throw new RuntimeException(对象序列化为 JSON 失败: obj.getClass().getName(), e); } } public static String toJsonPretty(Object obj) { if (obj null) { return null; } try { return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (JsonProcessingException e) { throw new RuntimeException(对象序列化为格式化 JSON 失败: obj.getClass().getName(), e); } }toJson是最常用的方法。toJsonPretty用于日志打印或调试生产环境不建议频繁使用因为它会额外增加输出体积。5.3 核心 APIJSON 转对象反序列化方法需要支持三种常见场景转单个对象。转泛型集合比如ListVideoInfo。转Map或JsonNode便于灵活取值。// 文件路径src/main/java/com/example/smartvideo/util/JsonUtils.java接上文 public static T T parseObject(String json, ClassT clazz) { if (!StringUtils.hasText(json)) { return null; } try { return OBJECT_MAPPER.readValue(json, clazz); } catch (JsonProcessingException e) { throw new RuntimeException(JSON 反序列化为 clazz.getName() 失败, e); } } public static T T parseObject(String json, TypeReferenceT typeReference) { if (!StringUtils.hasText(json)) { return null; } try { return OBJECT_MAPPER.readValue(json, typeReference); } catch (JsonProcessingException e) { throw new RuntimeException(JSON 反序列化失败目标类型: typeReference.getType(), e); } } public static T ListT parseList(String json, ClassT clazz) { if (!StringUtils.hasText(json)) { return null; } try { return OBJECT_MAPPER.readValue(json, new TypeReferenceListT() {}); } catch (JsonProcessingException e) { throw new RuntimeException(JSON 反序列化为 List clazz.getName() 失败, e); } } public static JsonNode parseTree(String json) { if (!StringUtils.hasText(json)) { return null; } try { return OBJECT_MAPPER.readTree(json); } catch (JsonProcessingException e) { throw new RuntimeException(JSON 解析为 JsonNode 失败, e); } } public static MapString, Object parseMap(String json) { return parseObject(json, new TypeReferenceMapString, Object() {}); }注意parseList里的new TypeReferenceListT() {}写法。你不能直接写ListT.class因为 Java 泛型擦除会导致运行时拿不到具体类型。TypeReference的工作原理是保存泛型签名让 Jackson 在反序列化时知道目标类型。5.4 核心 API大模型输出清洗与解析这是智能视频推荐小程序里最实用的一个方法。LangChain4j 和 SpringAI 返回的文本可能不是纯 JSON我们需要先提取出 JSON 片段。// 文件路径src/main/java/com/example/smartvideo/util/JsonUtils.java接上文 public static String extractJsonString(String rawText) { if (!StringUtils.hasText(rawText)) { return null; } String text rawText.trim(); // 去掉 Markdown 代码块标记 if (text.startsWith()) { int firstNewLine text.indexOf(\n); if (firstNewLine 0) { text text.substring(firstNewLine 1); } if (text.endsWith()) { text text.substring(0, text.length() - 3); } text text.trim(); } // 从第一个 { 开始截取 int start text.indexOf({); if (start 0) { throw new RuntimeException(无法从大模型输出中提取 JSON: rawText); } // 从最后一个 } 结束截取 int end text.lastIndexOf(}); if (end 0 || end start) { throw new RuntimeException(无法从大模型输出中提取 JSON: rawText); } return text.substring(start, end 1); } public static T T parseAiJson(String rawText, ClassT clazz) { String json extractJsonString(rawText); return parseObject(json, clazz); }这里采用了最稳妥的截取方式找第一个{和最后一个}。优点是简单可靠不怕模型在前面加废话、后面加补充说明。缺点是不能处理一个文本里包含多个 JSON 对象的情况但在推荐理由生成、用户画像解析这些单个 JSON 场景下完全够用。5.5 业务代码调用示例假设我们让大模型生成视频推荐理由返回格式约定为{ videoId: 1001, reason: 根据你最近观看的科技测评内容推荐这部人工智能纪录片。, confidence: 0.92, tags: [AI, 纪录片] }定义对应的 DTO// 文件路径src/main/java/com/example/smartvideo/model/RecommendReason.java package com.example.smartvideo.model; import lombok.Data; import java.util.List; Data public class RecommendReason { private Long videoId; private String reason; private Double confidence; private ListString tags; }在 Service 层调用// 文件路径src/main/java/com/example/smartvideo/service/RecommendService.java片段 public RecommendReason parseAiReason(String aiOutput) { RecommendReason reason JsonUtils.parseAiJson(aiOutput, RecommendReason.class); if (reason null || reason.getVideoId() null) { throw new RuntimeException(AI 推荐理由解析失败输出内容: aiOutput); } return reason; }这样就把大模型文本和 Java 对象之间建立了一个稳定的转换通道。即使模型偶尔输出不规整工具类也能兜底提取。5.6 统一响应结构示例后端接口通常需要统一响应格式。定义一个泛型响应类// 文件路径src/main/java/com/example/smartvideo/common/Result.java package com.example.smartvideo.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }Controller 中直接返回Result对象SpringMVC 会自动调用 Jackson 序列化GetMapping(/recommend) public ResultListRecommendReason recommend(RequestParam Long userId) { ListRecommendReason reasons recommendService.getReasonsByUserId(userId); return Result.success(reasons); }最终前端收到的 JSON 是{ code: 200, message: success, data: [ { videoId: 1001, reason: 根据你最近观看的科技测评内容推荐这部人工智能纪录片。, confidence: 0.92, tags: [AI, 纪录片] } ] }6. 运行结果与效果验证6.1 编写单元测试不要等接口联调才发现 JSON 解析问题。建议直接为JsonUtils写单元测试。以下测试类可以作为模板// 文件路径src/test/java/com/example/smartvideo/util/JsonUtilsTest.java package com.example.smartvideo.util; import com.example.smartvideo.model.VideoInfo; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.*; class JsonUtilsTest { Test void testToJsonAndParseObject() { VideoInfo videoInfo new VideoInfo(); videoInfo.setId(1L); videoInfo.setTitle(Spring Boot 实战); videoInfo.setCoverUrl(http://example.com/cover.png); videoInfo.setScore(new BigDecimal(9.2)); videoInfo.setTags(Arrays.asList(Java, Spring)); videoInfo.setCreateTime(LocalDateTime.of(2025, 1, 1, 12, 0, 0)); videoInfo.setInternalRemark(内部备注不应出现在 JSON 里); String json JsonUtils.toJson(videoInfo); System.out.println(序列化结果: json); // 断言内部字段被忽略 assertFalse(json.contains(internalRemark)); // 断言日期格式正确 assertTrue(json.contains(2025-01-01 12:00:00)); // 反序列化回对象 VideoInfo parsed JsonUtils.parseObject(json, VideoInfo.class); assertNotNull(parsed); assertEquals(Long.valueOf(1L), parsed.getId()); assertEquals(Spring Boot 实战, parsed.getTitle()); } Test void testParseJsonArray() { String json [{\id\:1,\title\:\视频A\},{\id\:2,\title\:\视频B\}]; ListVideoInfo list JsonUtils.parseList(json, VideoInfo.class); assertNotNull(list); assertEquals(2, list.size()); assertEquals(视频A, list.get(0).getTitle()); } Test void testExtractJsonFromAiOutput() { String aiOutput 以下是推荐结果\njson\n{\videoId\:1001,\reason\:\推荐理由\,\confidence\:0.92}\n\n希望对你有帮助; String json JsonUtils.extractJsonString(aiOutput); System.out.println(提取后的 JSON: json); assertNotNull(json); assertTrue(json.startsWith({)); assertTrue(json.endsWith(})); } }6.2 预期输出与验证点运行测试后控制台会打印类似结果序列化结果: {id:1,title:Spring Boot 实战,coverUrl:http://example.com/cover.png,score:9.2,tags:[Java,Spring],createTime:2025-01-01 12:00:00}验证点有三个internalRemark字段没有出现在 JSON 中说明JsonIgnore生效。createTime输出为2025-01-01 12:00:00说明日期格式控制成功。null字段被忽略因为我们在ObjectMapper中配置了NON_NULL。如果createTime输出成了数组格式或时间戳优先检查JavaTimeModule是否注册成功以及DateTimeFormatter的 pattern 是否正确。6.3 配合 SpringAI / LangChain4j 的验证方式调用大模型接口时建议先打印原始返回内容再走工具类解析String rawOutput aiChatModel.call(prompt); log.info(AI 原始输出: {}, rawOutput); RecommendReason reason JsonUtils.parseAiJson(rawOutput, RecommendReason.class); log.info(解析后对象: {}, JsonUtils.toJson(reason));这样你既能直观看到模型输出长什么样也能快速定位解析失败是模型问题还是工具类问题。7. 常见问题与排查思路JSON 工具类看起来简单实际使用时问题并不少。下面按经验整理高频问题。问题现象可能原因排查方式解决方案LocalDateTime序列化成数组[2025,1,1,12,0]未注册JavaTimeModule或未配置格式打印序列化结果检查 ObjectMapper 配置注册JavaTimeModule并添加LocalDateTimeSerializer反序列化报UnrecognizedPropertyException目标对象缺少多余字段且未关闭FAIL_ON_UNKNOWN_PROPERTIES查看异常信息中的字段名设置FAIL_ON_UNKNOWN_PROPERTIES为 false大模型返回内容解析失败输出不是纯 JSON包含解释文字或 Markdown 标记打印原始输出观察首尾字符使用extractJsonString提取后再解析序列化出现循环引用Infinite recursion两个对象互相引用比如父子结构查看堆栈确认字段关系在关联字段上加JsonIgnore或JsonManagedReference日期格式固定为yyyy-MM-dd HH:mm:ss但接口返回时间戳application.yml配置对LocalDateTime不生效检查是否使用 Java 8 时间类型在实体字段加JsonFormat或手动注册模块JSON 中包含反斜杠转义字符导致前端解析失败字符串中包含特殊字符序列化时被自动转义打印原始 JSON 字符串这是 JSON 正常行为前端用JSON.parse即可parseList返回null传入 JSON 为空字符串或空白字符检查StringUtils.hasText判断逻辑工具类中已做空字符串保护但调用方仍要检查入参泛型ListT解析后元素是LinkedHashMap而不是目标类型直接使用List.class反序列化检查代码是否用了TypeReference改用new TypeReferenceListVideoInfo() {}序列化时Boolean字段变成isXxx结构Lombok 的getter生成规则与 Jackson 耦合查看实体类字段和getter命名明确设计字段名或使用JsonProperty指定除了表格里的问题还有一个隐藏很深的坑大模型有时会输出NaN或Infinity这类非标准 JSON 数值。Jackson 默认是拒绝反序列化这类值的。如果这种概率很低没必要在工具类里全局放开建议在调用大模型时通过 Prompt 约束输出格式或者在解析前做一次正则替换。8. 最佳实践与工程建议8.1 设计统一返回结构但不滥用ResultT统一响应结构非常有价值因为它让前端 Uniapp 可以用同一套逻辑处理成功和失败。但要注意两点第一分页数据、错误详情等不要塞进data里变成一个复杂的嵌套对象尽量单独定义字段或 DTO。第二不要把Result用在大模型流式输出的场景。流式输出每条消息都包一层Result会增加无谓开销也容易把简单的问题复杂化。8.2 字段命名规范前后端约定优先在后端实体类里推荐字段使用驼峰命名比如coverUrl。但如果第三方接口返回cover_url不要改实体类字段名而是在字段上加JsonProperty(cover_url)JsonProperty(cover_url) private String coverUrl;这样既能保持 Java 代码风格一致又能对接外部接口。比手工写一堆get(cover_url)要清晰得多。8.3 日志打印安全敏感字段必须脱敏JSON 序列化经常用于打印日志。这时要特别小心密码、token、手机号等敏感字段一旦被序列化就会进日志系统成为安全隐患。推荐三种处理方式在实体类敏感字段上标注JsonIgnore彻底不输出。单独定义日志专用 DTO只包含必要字段。在日志输出前调用脱敏工具将手机号中间四位替换为****。8.4 ObjectMapper 尽量复用ObjectMapper是线程安全的构建成本较高不要每次调用都new一个。最佳实践是像本文工具类一样在静态代码块中初始化一个全局实例并复用。如果项目里需要不同配置的 ObjectMapper可以定义多个命名实例比如objectMapperForThirdParty、objectMapperForLog但不要随意创建。8.5 大模型 JSON 输出的二次校验我们虽然提供了extractJsonString但它只能解决格式不规整的问题不能解决内容不合法的问题。大模型返回的 JSON 即使解析成功字段值也可能不符合业务预期比如videoId是负数、confidence大于 1。因此解析之后一定要做业务校验必填字段是否为 null。枚举值是否在合法范围内。外键 ID 是否存在。建议把校验逻辑放在独立的 Service 方法里而不是塞进JsonUtils。这样工具类保持纯粹业务规则也更容易测试。8.6 约定优于配置让团队统一调用入口在团队协作中建议在 README 或代码规范文档里明确约定所有 JSON 序列化反序列化只能调用JsonUtils。禁止在业务代码中直接使用ObjectMapper或JSONObject.parseObject。禁止手工拼接 JSON 字符串。新增实体类时如果字段不想暴露给前端必须添加JsonIgnore。这样长期维护时改动 JSON 策略只需要改一个类风险可控。9. 总结与后续学习方向本文围绕智能视频推荐小程序的后端开发把 JSON 工具类的设计、实现、测试和排查讲了一遍。核心要点可以归纳为四条第一自定义 JSON 工具类的真正价值不是封装 API而是统一调用入口、统一异常处理、统一序列化策略。第二基于 Jackson 封装时重点处理好 Java 8 时间类型、未知字段、空值策略三个问题。这三件事最容易在不同环境出现行为不一致。第三SpringAI 和 LangChain4j 场景下大模型输出可能是夹带私货的文本extractJsonString这种提取方法很有实战价值。第四JSON 工具类要放在安全框架内使用字段脱敏、业务校验、团队规范缺一不可。下一步建议你按这个顺序继续深入把JsonUtils跑进自己的项目补全单元测试。对接一个真实的大模型接口打印原始输出观察需要清洗的特殊情况。研究JsonProperty、JsonIgnore、JsonFormat的更多组合用法因为它们在复杂嵌套对象中非常有用。如果项目中 JSON 处理性能成为瓶颈再考虑对比 Fastjson2 的序列化性能但不要一开始就为了性能牺牲可维护性。代码永远是为业务服务的。JSON 工具类写得好系统对接大模型、前端联调都会顺畅很多。建议把这篇文章收藏等你在智能视频推荐小程序项目里遇到 JSON 解析问题时再对照着排查一遍会比自己摸索快不少。