Jackson如何为任意对象所有属性名(含嵌套)动态追加指定后缀?
问题背景
现有如下Java类定义:
@Value public class Example { @JsonProperty("StrValue") String strValue; @JsonProperty("Internal") Internal internal; @Value public static class Internal { @JsonProperty("InternalValue") String internalValue; } }
默认序列化后得到的JSON格式如下:
"Example" : { "StrValue": "v1", "Internal": { "InternalValue": "v2" } }
需要生成两种不同版本的序列化结果:一种为所有属性名(含根节点、嵌套对象属性)追加字符串First后缀,另一种追加Second后缀,预期格式如下:
"ExampleFirst" : { "StrValueFirst": "v1", "InternalFirst": { "InternalValueFirst": "v2" } }
要求该能力支持任意类的任意对象,且对所有嵌套属性生效。
实现方案
你可以通过Jackson自带的PropertyNamingStrategy + 根节点映射配置实现,比自定义BeanSerializerModifier复杂度低很多,完全满足通用、嵌套生效的需求。
步骤1:自定义后缀追加命名策略
import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.introspect.AnnotatedField; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; public class SuffixAppendNamingStrategy extends PropertyNamingStrategy { private final String suffix; public SuffixAppendNamingStrategy(String suffix) { this.suffix = suffix; } @Override public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) { return defaultName + suffix; } @Override public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) { return defaultName + suffix; } @Override public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) { return defaultName + suffix; } @Override public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) { return defaultName + suffix; } }
步骤2:自定义根节点后缀映射
import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.RootNameMapper; public class SuffixAppendRootNameMapper implements RootNameMapper { private final String suffix; public SuffixAppendRootNameMapper(String suffix) { this.suffix = suffix; } @Override public String getRootName(MapperConfig<?> config, AnnotatedClass annotatedClass) { String defaultRootName = config.getAnnotationIntrospector().findRootName(annotatedClass).getSimpleName(); return defaultRootName == null ? annotatedClass.getRawType().getSimpleName() + suffix : defaultRootName + suffix; } }
步骤3:封装Mapper生成工具
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; public class SuffixSerializerFactory { private static final ObjectMapper BASE_MAPPER = new ObjectMapper(); public static ObjectMapper getMapperWithSuffix(String suffix) { ObjectMapper mapper = BASE_MAPPER.copy(); // 开启根节点包裹 mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); // 配置属性命名策略 mapper.setPropertyNamingStrategy(new SuffixAppendNamingStrategy(suffix)); // 配置根节点名称映射 mapper.setRootNameMapper(new SuffixAppendRootNameMapper(suffix)); return mapper; } }
测试使用
// 构造测试对象 Example.Internal internal = new Example.Internal("v2"); Example example = new Example("v1", internal); // 生成带First后缀的JSON ObjectMapper firstMapper = SuffixSerializerFactory.getMapperWithSuffix("First"); String firstResult = firstMapper.writerWithDefaultPrettyPrinter().writeValueAsString(example); // 生成带Second后缀的JSON ObjectMapper secondMapper = SuffixSerializerFactory.getMapperWithSuffix("Second"); String secondResult = secondMapper.writerWithDefaultPrettyPrinter().writeValueAsString(example);
注意事项
- ObjectMapper是线程安全的,你可以将不同后缀的Mapper实例做单例缓存,避免重复创建开销
- 该实现会优先保留
@JsonProperty指定的属性名再追加后缀,和原有序列化逻辑完全兼容 - 如果不需要根节点包裹,仅需属性加后缀,移除
enable(SerializationFeature.WRAP_ROOT_VALUE)和RootNameMapper的配置即可
内容的提问来源于stack exchange,提问作者Haf
相关产品推荐
相关产品推荐

