美文网首页
Jackson自定义序列化注解(2)- Map扁平化到Bean中

Jackson自定义序列化注解(2)- Map扁平化到Bean中

作者: 小胖学编程 | 来源:发表于2022-04-09 19:06 被阅读0次

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

    要求图.png

    如上图所示,这样对象Y就可以动态的选择序列化的字段。

    本文主要实现Map对象(A,B)如何扁平化到Bean中?且扁平化的过程中,需要完成驼峰->蛇形的转换。
    注:借鉴@JsonProperty的实现思路。

    实现代码

    引入依赖:

    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>31.0.1-jre</version>
    </dependency>
    

    自定义注解:

    @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<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
                List<BeanPropertyWriter> 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<CaseFormat> SUPPORT_WITH_NUMBER_FORMAT =
                Sets.newHashSet(CaseFormat.LOWER_UNDERSCORE, CaseFormat.UPPER_UNDERSCORE);
    
        private static Map<String/*CaseFormatName*/, ConcurrentHashMap<String/*sourceName*/, String/*targetName*/>>
                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<String, Object> map = (Map<String, Object>) this._accessorMethod.invoke(bean);
                for (Map.Entry<String, Object> 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<String, String> 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
         * <p>
         * 此处转化为蛇形时,数字依旧使用_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<String, Object> extraMap;
    
        @DynamicCaseFormatFields(format = CaseFormat.LOWER_UNDERSCORE, withNumber = true)
        private Map<String, String> 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<String, Object> 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<String, String> 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中

          本文链接:https://www.haomeiwen.com/subject/htgpsrtx.html