如何在Jackson自定义JsonSerializer中序列化两个字段避免JSON字段重复
Jackson单个字段自定义序列化器同步生成两个字段避免重复输出
需求描述
使用Jackson进行JSON序列化时,希望通过给单个POJO字段标注的自定义JsonSerializer同时完成两个POJO字段的序列化,避免第二个字段被默认序列化逻辑处理,同时尽可能保持POJO代码简洁。
代码示例
POJO代码
public class Entity{ // 两个字段是否加@JsonProperty都不影响行为 @JsonSerialize(using=MySerializer.class) @HashTo("hashedKuku") private String kuku; private String hashedKuku; // 两个字段均有公共Getter、Setter }
自定义序列化器MySerializer代码
public class MySerializer extends JsonSerializer<String> { // 为简化省略try-catch等非核心代码 @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(value); final String currentFieldName = gen.getOutputContext().getCurrentName(); final Field declaredField = gen.getCurrentValue().getClass().getDeclaredField(currentFieldName); final HashTo annotation = declaredField.getAnnotation(HashTo.class); final String hashFieldName; if(annotation!=null && !(hashFieldName=annotation.value()).isEmpty()) { // gen.writeStringField(hashFieldName, new Hasher().getHashedValue(value)); // 或者 serializers.defaultSerializeField(hashFieldName, new Hasher().getHashedValue(value), gen); } }
预期与实际结果
预期序列化结果
{ "kuku": "someValue", "hashedKuku": "someHash" }
实际序列化结果(重复字段问题)
{ "kuku": "someValue", "hashedKuku": "someHash", "hashedKuku": null }
问题原因
MySerializer已经写入了hashedKuku字段,同时Jackson默认序列化器也会对hashedKuku字段执行序列化操作。
使用约束
- 自定义MySerializer只能标注在字段上,不能使用类级序列化器;
- POJO需尽量简洁,仅在kuku字段上添加2个注解即可。
不符合要求的可行方案
- 给hashedKuku字段添加
@JsonProperty(JsonProperty.Access.WRITE_ONLY)注解; - 给hashedKuku字段添加
@JsonIgnore注解; - 给hashedKuku的getter方法添加
@JsonIgnore注解;
其余方案大多存在过度设计的问题,也不符合要求。
解决方案
通过自定义BeanSerializerModifier动态过滤被@HashTo指向的字段,无需修改POJO代码,完全符合约束要求:
步骤1:补充@HashTo注解定义
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface HashTo { String value(); }
步骤2:自定义BeanSerializerModifier实现字段过滤
public class HashToFieldFilterModifier extends BeanSerializerModifier { @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { Set<String> ignoreFields = new HashSet<>(); // 收集所有被@HashTo指向的目标字段 for (Field field : beanDesc.getBeanClass().getDeclaredFields()) { HashTo hashTo = field.getAnnotation(HashTo.class); if (hashTo != null && !hashTo.value().isBlank()) { ignoreFields.add(hashTo.value()); } } // 过滤掉需要忽略的字段,默认序列化器不会再处理这些字段 return beanProperties.stream() .filter(property -> !ignoreFields.contains(property.getName())) .collect(Collectors.toList()); } }
步骤3:注册到ObjectMapper
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new SimpleModule() { @Override public void setupModule(SetupContext context) { super.setupModule(context); context.addBeanSerializerModifier(new HashToFieldFilterModifier()); } });
方案优势
- 完全符合使用约束:POJO仅需在kuku字段保留两个注解,无需修改hashedKuku字段的任何配置
- 通用适配:所有标注了
@HashTo的字段都会自动处理对应目标字段的过滤,无硬编码逻辑 - 原有序列化器逻辑无需改动,可直接复用
内容的提问来源于stack exchange,提问作者Julian Broudy
相关产品推荐
相关产品推荐

