Jackson序列化Map<Investment, BigDecimal>出现对象引用问题咨询
Jackson序列化嵌套对象Map键异常问题解决方案
问题根因
你遇到的问题由两个底层机制导致:
- Jackson默认序列化Map类型时,仅会调用键对象的
toString()方法作为键的输出值,不会对键对象做结构化序列化,你看到的com.financial.detail.Investment@5d465e4b就是Investment类默认的toString()返回值。 - Investment和MutualFund互相持有对方的引用,直接序列化会触发循环依赖,导致栈溢出异常。
问题1:是否可以仅通过Jackson注解解决?
可以,仅需少量注解即可满足需求,有两种常用实现方案:
方案1:输出结构化键值对数组(兼容JSON规范,优先推荐)
JSON规范要求对象的键必须为字符串类型,如果你可以接受将Map序列化为[ [投资对象1, 数值1], [投资对象2, 数值2] ]的数组格式,直接添加两个注解即可:
- 给两个实体类添加
@JsonIdentityInfo注解处理循环依赖,避免栈溢出:
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.annotation.JsonFormat; // 用shortName作为对象唯一标识,也可改用ObjectIdGenerators.UUIDGenerator生成唯一ID @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "shortName") public class Investment implements Serializable{ // 原有代码不变 } // 同上添加注解 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "expenseRatio") public class MutualFund implements Serializable{ // 给underlyingInvestments添加数组格式序列化注解 @JsonFormat(shape = JsonFormat.Shape.ARRAY) private Map<Investment, BigDecimal> underlyingInvestments; // 原有代码不变 }
方案2:将Investment序列化为JSON字符串作为Map键
如果你需要保留Map的对象结构,直接给underlyingInvestments字段指定键序列化器即可:
public class MutualFund implements Serializable{ // 用Jackson自带的ToStringSerializer会自动调用对象的JSON序列化结果作为键 @JsonSerialize(keyUsing = ToStringSerializer.class) private Map<Investment, BigDecimal> underlyingInvestments; // 原有代码不变 }
注意:该方案需要你重写Investment的toString()方法,返回你需要的JSON格式字符串,或者自定义简单的键序列化器实现。
问题2:自定义序列化器的最优实现思路
如果需要更灵活的序列化逻辑,自定义时遵循以下思路即可规避循环依赖问题:
- 不要手动维护对象引用缓存,复用Jackson内置的对象标识处理能力:自定义序列化器时通过
SerializerProvider获取对应类型的序列化器,Jackson会自动识别已经序列化过的对象,避免循环输出。 - 键序列化器仅负责Investment的结构化输出:
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; public class InvestmentKeySerializer extends StdSerializer<Investment> { public InvestmentKeySerializer() { super(Investment.class); } @Override public void serialize(Investment investment, JsonGenerator gen, SerializerProvider provider) throws IOException { // 直接复用Jackson的序列化逻辑,自动处理循环依赖 provider.findValueSerializer(Investment.class).serialize(investment, gen, provider); } }
- 给目标字段指定自定义序列化器即可:
@JsonSerialize(keyUsing = InvestmentKeySerializer.class) private Map<Investment, BigDecimal> underlyingInvestments;
内容的提问来源于stack exchange,提问作者Dave
相关产品推荐
相关产品推荐

