You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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类型,和序列化器的目标类型不匹配,因此抛出类型转换异常,且序列化器根本不会被执行——字段类型和序列化器处理类型不匹配。

修复步骤

  1. 修正序列化器的标注位置
    自定义序列化器是用来处理AbilityWrapper的,二选一即可:

    • 在AbilityWrapper类上添加@JsonSerialize(using = AbilityWrapperSerializer.class);
    • 给AbilityWrapper里的ability字段单独添加针对Model.Ability类型的序列化器,不要给整个Wrapper类乱加。
  2. 优先使用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;
    }
    
  3. 如果必须手动实现序列化器

    • 确保序列化器的目标类型和字段类型匹配:比如要序列化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 03:52:17