Jackson自定义序列化注解(2)- Map扁平化到Bean中(&格式转换)

上一篇:Jackson自定义序列化注解(1)- Map的key驼峰蛇形的转换中,实现了对Map的key的参数转换,但是总是存在一些骚操作:

要求图.png

如上图所示,这样对象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);
    }
}

你可能感兴趣的:(Jackson自定义序列化注解(2)- Map扁平化到Bean中(&格式转换))