Java含Proto的类序列化为JSON响应时遇循环引用与类型转换问题
问题:包含Proto对象的AbilityWrapper序列化HTTP JSON响应异常排查
问题场景
尝试把包含Proto对象的AbilityWrapper类序列化成HTTP JSON响应:先通过YAMLMapper读取YAML文件反序列化得到类实例,调试时Proto对象已正确保存,但返回响应时触发循环引用异常;添加自定义序列化器后,又出现类型转换异常,且调试时根本没进入序列化器逻辑。
异常信息
第一次循环引用异常
handleException
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: java.util.ArrayList[0]->abilities.ability.AbilityWrapper["rule"]->abilities.Model$AbilityDto["unknownFields"]->com.google.protobuf.UnknownFieldSet["defaultInstanceForType"])
添加序列化器后的类型转换异常
Resolved
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: class.Message$AbilityDto cannot be cast to class.AbilityWrapper (Abilities$abilityDto and AbilityWrapper are in unnamed module of loader 'app'); nested exception is com.fasterxml.jackson.databind.JsonMappingException: class Abilities$abilityDto cannot be cast to class .abilities.AbilityWrapper (Abilities$abilityDto and abilities.AbilityWrapper are in unnamed module of loader 'app') (through reference chain: java.util.ArrayList[0]->ability.AbilityWrapper["ability"])
相关代码
从YAML创建实例的代码
for (String yamlString : strings) { ObjectMapper mapper = new YAMLMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); try { AbilityWrapper ability = mapper.readValue(yamlString, PredefinedRuleWrapper.class); if (ability.isActive()) { abilities.add(ability); } } catch (Exception e) { e.printStackTrace(); } }
原始AbilityWrapper类定义
@Getter @Setter @NoArgsConstructor public class AbilityWrapper { @JsonDeserialize(using = AbilityProtoDeserializer.class) private Model.Ability ability; private boolean active; public static class AbilityProtoDeserializer extends JsonDeserializer<Model.Ability> { @Override public Model.Ability deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Model.Builder builder = Model.Ability.newBuilder(); JsonFormat.parser().merge(p.readValueAsTree().toString(), builder); return builder.build(); } } }
添加序列化器后的类代码
@Getter @Setter @NoArgsConstructor public class PredefinedRuleWrapper { @JsonDeserialize(using = AbilityProtoDeserializer.class) @JsonSerialize(using = PredefinedRuleWrapperSerializer.class) private Rules.RuleDto rule; . . . public static class AbilityWrapperSerializer extends StdSerializer<AbilityWrapper> { public AbilityWrapperSerializer() { super(AbilityWrapper.class); } @Override public void serialize(AbilityWrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException { ObjectMapper mapper = new ObjectMapper(); ObjectNode node = mapper.createObjectNode(); node.put("active", value.isActive()); // Convert the RuleDto to JSON string using protobuf's JsonFormat String ruleJson = JsonFormat.printer().print(value.getAbility()); JsonNode abilityNode = mapper.readTree(AbilityJson); node.set("ability", abilityNode); mapper.writeValue(gen, node); } } }
问题分析与解决办法
循环引用异常原因
Proto对象(比如UnknownFieldSet)内部存在自引用结构,Jackson默认序列化时会递归遍历所有字段,触发循环引用检测,抛出异常。
类型转换异常原因
添加序列化器后的代码存在明显类混淆:把处理AbilityWrapper类型的序列化器,错误标注在了PredefinedRuleWrapper的rule字段上,而rule字段是Rules.RuleDto类型,和序列化器的目标类型不匹配,因此抛出类型转换异常,且序列化器根本不会被执行——字段类型和序列化器处理类型不匹配。
修复步骤
修正序列化器的标注位置
自定义序列化器是用来处理AbilityWrapper的,二选一即可:- 在
AbilityWrapper类上添加@JsonSerialize(using = AbilityWrapperSerializer.class); - 给
AbilityWrapper里的ability字段单独添加针对Model.Ability类型的序列化器,不要给整个Wrapper类乱加。
- 在
优先使用Jackson官方Protobuf模块
手动写序列化/反序列化器容易出错,推荐直接用Jackson的jackson-datatype-protobuf模块,它能自动处理Proto对象的序列化和反序列化,还能避免循环引用问题:- 先添加Maven依赖:
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-protobuf</artifactId> <version>与你的Jackson版本保持一致</version> </dependency> - 读取YAML时注册模块:
for (String yamlString : strings) { ObjectMapper mapper = new YAMLMapper(); mapper.registerModule(new ProtobufModule()); // 注册Protobuf模块 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); try { AbilityWrapper ability = mapper.readValue(yamlString, AbilityWrapper.class); if (ability.isActive()) { abilities.add(ability); } } catch (Exception e) { e.printStackTrace(); } }
此时
AbilityWrapper类可简化为:@Getter @Setter @NoArgsConstructor public class AbilityWrapper { private Model.Ability ability; private boolean active; }- 先添加Maven依赖:
如果必须手动实现序列化器
- 确保序列化器的目标类型和字段类型匹配:比如要序列化
Model.Ability,就写继承StdSerializer<Model.Ability>的序列化器,再标注在ability字段的@JsonSerialize上; - 不要在序列化器里新建
ObjectMapper,用provider.getDefaultObjectMapper()即可,避免重复创建带来的问题; - 修正代码中的变量错误:比如
AbilityJson应该是ruleJson,否则会报变量未定义。
- 确保序列化器的目标类型和字段类型匹配:比如要序列化
手动序列化器的修正示例
@Getter @Setter @NoArgsConstructor @JsonSerialize(using = AbilityWrapper.AbilityWrapperSerializer.class) // 标注在类上 public class AbilityWrapper { @JsonDeserialize(using = AbilityProtoDeserializer.class) private Model.Ability ability; private boolean active; public static class AbilityProtoDeserializer extends JsonDeserializer<Model.Ability> { @Override public Model.Ability deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Model.Ability.Builder builder = Model.Ability.newBuilder(); JsonFormat.parser().merge(p.readValueAsTree().toString(), builder); return builder.build(); } } public static class AbilityWrapperSerializer extends StdSerializer<AbilityWrapper> { public AbilityWrapperSerializer() { super(AbilityWrapper.class); } @Override public void serialize(AbilityWrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); gen.writeBooleanField("active", value.isActive()); // 用Proto的JsonFormat转成字符串,再写入JSON String abilityJson = JsonFormat.printer().print(value.getAbility()); JsonNode abilityNode = provider.getDefaultObjectMapper().readTree(abilityJson); gen.writeFieldName("ability"); provider.getDefaultObjectMapper().writeValue(gen, abilityNode); gen.writeEndObject(); } } }
内容的提问来源于stack exchange,提问作者Liron Tal
相关产品推荐
相关产品推荐

