上一篇:Jackson自定义序列化注解(1)- Map的key驼峰蛇形的转换中,实现了对Map的key的参数转换,但是总是存在一些骚操作:
如上图所示,这样对象Y就可以动态的选择序列化的字段。
本文主要实现Map对象(A,B)如何扁平化到Bean中?且扁平化的过程中,需要完成驼峰->蛇形的转换。
注:借鉴@JsonProperty的实现思路。
实现代码
引入依赖:
com.google.guava
guava
31.0.1-jre
自定义注解:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface DynamicCaseFormatFields {
CaseFormat sourceFormat() default CaseFormat.LOWER_CAMEL;
CaseFormat format() default CaseFormat.LOWER_CAMEL;
boolean withNumber() default false;
}
public class DynamicCaseFormatFieldsBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List changeProperties(SerializationConfig config, BeanDescription beanDesc,
List beanProperties) {
return beanProperties.stream().map(bpw -> {
DynamicCaseFormatFields
annotation = bpw.getAnnotation(DynamicCaseFormatFields.class);
if (annotation == null || !bpw.getType().isTypeOrSubTypeOf(Map.class)
|| !bpw.getType().getKeyType().isTypeOrSubTypeOf(String.class)) {
return bpw;
}
return new DynamicCaseFormatFieldsBeanPropertyWriter(bpw, annotation);
}).collect(Collectors.toList());
}
}
public class DynamicCaseFormatFieldsBeanPropertyWriter extends BeanPropertyWriter {
private static final long serialVersionUID = 3802011467106097526L;
private final transient DynamicCaseFormatFields annotation;
private static final Set SUPPORT_WITH_NUMBER_FORMAT =
Sets.newHashSet(CaseFormat.LOWER_UNDERSCORE, CaseFormat.UPPER_UNDERSCORE);
private static Map >
NAME_CACHE = new ConcurrentHashMap<>();
public DynamicCaseFormatFieldsBeanPropertyWriter(BeanPropertyWriter base, DynamicCaseFormatFields annotation) {
super(base);
this.annotation = annotation;
}
@Override
public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) {
try {
Map map = (Map) this._accessorMethod.invoke(bean);
for (Map.Entry entry : map.entrySet()) {
gen.writeFieldName(computeTargetNameIfAbsent(annotation, entry.getKey()));
gen.writeObject(entry.getValue());
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private String generateCacheKey(DynamicCaseFormatFields annotation) {
return String.join("", annotation.format().name(),
annotation.withNumber() && SUPPORT_WITH_NUMBER_FORMAT.contains(CaseFormat.LOWER_UNDERSCORE) ? "_NUMBER"
: "");
}
private String computeTargetNameIfAbsent(DynamicCaseFormatFields annotation, String fieldName) {
ConcurrentHashMap map =
NAME_CACHE.computeIfAbsent(generateCacheKey(annotation), k -> new ConcurrentHashMap<>());
if (annotation.sourceFormat().equals(annotation.format()) && !SUPPORT_WITH_NUMBER_FORMAT.contains(
annotation.sourceFormat())) {
return fieldName;
}
return map.computeIfAbsent(fieldName, k -> computeTargetName(annotation, k));
}
/**
* com.google.common.base.CaseFormat 从驼峰转下划线,遇到带数字的会导致问题的错误
* 具体的问题是,数字后边会加上正确的下划线,但是前边不会,比如
* id4User 应该转成 id_4_user
* 但实际会被转成 id4_user
*
* 此处转化为蛇形时,数字依旧使用_Number_进行区分。
*/
private String computeTargetName(DynamicCaseFormatFields annotation, String fieldName) {
String key = annotation.sourceFormat().to(annotation.format(), fieldName);
if (!annotation.withNumber() || !SUPPORT_WITH_NUMBER_FORMAT.contains(annotation.format())) {
return key;
}
//处理属性中带数字的格式
Matcher m = Pattern.compile("(?<=[a-z])[0-9]").matcher(key);
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "_" + m.group());
}
m.appendTail(sb);
return sb.toString();
}
}
public class DynamicCaseFormatFieldsModules {
public static SimpleModule create() {
return new SimpleModule("dynamicCaseFormatFieldsModule")
.setSerializerModifier(new DynamicCaseFormatFieldsBeanSerializerModifier());
}
}
使用方式
@Data
public class User {
private String nameInfo;
private String ageInfo;
private Account account;
/**
* map数据扁平化
*/
@DynamicCaseFormatFields(format = CaseFormat.LOWER_UNDERSCORE)
private Map extraMap;
@DynamicCaseFormatFields(format = CaseFormat.LOWER_UNDERSCORE, withNumber = true)
private Map cache;
@Data
public static class Account {
private Long accountId;
@JsonProperty("name_4_user")
private String name4User;
}
public static User builder() {
User user = new User();
user.setNameInfo("coder");
user.setAgeInfo("28");
Account account = new Account();
account.setAccountId(1001L);
account.setName4User("liming");
user.setAccount(account);
Map extra = new HashMap<>();
extra.put("id4User", "123");
extra.put("userAge", 23);
extra.put("myPrice", 12.345);
extra.put("uId", 1200L);
extra.put("account", account);
user.setExtraMap(extra);
Map cache = new HashMap<>();
cache.put("id4Cache", "123");
cache.put("name4Cache", "456");
user.setCache(cache);
return user;
}
}
测试代码:
public class TestEx {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Jackson2ObjectMapperBuilder.json()
//添加自定义注解(动态转换器)
.modulesToInstall(DynamicCaseFormatFieldsModules.create())
.configure(objectMapper);
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(User.builder());
System.out.println(json);
}
}