You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jackson复杂嵌套结构序列化反序列化代码优化方案问询

问题描述

我正在为游戏社区开发基于Spring的可扩展、模块化小型自定义CMS后端,需要将模块配置、内容等信息以JSON字符串形式存储到关系型数据库(无法使用NoSQL,指定用Java开发)。当前要存储<String, Object>类型的嵌套Map,序列化相对容易,但反序列化时Jackson默认返回LinkedHashMap,我自己写了一套实现,但方案比较粗糙,现在寻求代码性能优化或更简洁的实现方式。


现有实现代码

ObjectWrapper类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ObjectWrapper {
    private String clazz;
    private Object value;
}

CustomHashMapJsonConverter类

@Log4j2
public class CustomHashMapJsonConverter implements AttributeConverter<HashMap<String, Object>, String> {

    private static ObjectMapper objectMapper = getDefaultObjectMapper();

    private static ObjectMapper getDefaultObjectMapper() {
        ObjectMapper defaultObjectMapper = new ObjectMapper();
        defaultObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return defaultObjectMapper;
    }

    @Override
    public String convertToDatabaseColumn(HashMap<String, Object> attributeMap) {
        String jsonResult = null;
        ObjectWrapper topLevelWrapper = wrapMapAndContents(attributeMap);
        ObjectWriter objectWriter = objectMapper.writer();
        objectWriter = objectWriter.with(SerializationFeature.INDENT_OUTPUT);
        try {
            jsonResult = objectWriter.writeValueAsString(objectMapper.valueToTree(topLevelWrapper));
        } catch (JsonProcessingException | IllegalArgumentException e) {
            log.error("Error while serializing a map: ", e);
        }
        return jsonResult;
    }

    @Override
    public HashMap<String, Object> convertToEntityAttribute(String dbJsonData) {

        HashMap<String, Object> resultMap = new HashMap<>();

        SimpleModule module = new SimpleModule();
        module.addDeserializer(ObjectWrapper.class, new CustomHashMapJsonDeserializer());
        objectMapper.registerModule(module);

        try {
            ObjectWrapper topLevelWrapper = objectMapper.readValue(dbJsonData, ObjectWrapper.class);
            resultMap = (HashMap<String, Object>) unwrapObjectWrapperContent(topLevelWrapper);
        } catch (JsonProcessingException | ClassNotFoundException | InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            log.error("Error while deserializing a retrieved JSON from the DB: ", e);
        }

        return resultMap;
    }

    public ObjectWrapper wrapMapAndContents(Map<String, Object> map) {
        HashMap<String, Object> resultMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() instanceof Map) {
                resultMap.put(entry.getKey(), wrapMapAndContents((Map<String, Object>) entry.getValue()));
            } else if (entry.getValue() instanceof List) {
                resultMap.put(entry.getKey(), wrapListAndContents((List<Object>) entry.getValue()));
            } else {
                resultMap.put(entry.getKey(), wrapObject(entry.getValue()));
            }
        }
        return wrapObject(resultMap);
    }

    public ObjectWrapper wrapListAndContents(List<Object> list) {
        ArrayList<Object> resultList = new ArrayList<>();
        for (Object listObject : list) {
            if (listObject instanceof Map) {
                resultList.add(wrapMapAndContents((Map<String, Object>) listObject));
            } else if (listObject instanceof List) {
                resultList.add(wrapListAndContents((List<Object>) listObject));
            } else {
                resultList.add(wrapObject(listObject));
            }
        }
        return wrapObject(resultList);
    }

    public ObjectWrapper wrapObject(Object object) {
        return new ObjectWrapper(object.getClass().toString().split(" ")[1], object);
    }

    public Object unwrapObjectWrapperContent(ObjectWrapper topLevelWrapper)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, SecurityException {
        Object topLevelObject = Class.forName(topLevelWrapper.getClazz()).cast(topLevelWrapper.getValue());
        if (topLevelObject instanceof Map) {
            Map<String, ObjectWrapper> topLevelMap = (Map<String, ObjectWrapper>) topLevelObject;
            Map<String, Object> newMap = (Map<String, Object>) Class.forName(topLevelWrapper.getClazz())
                    .getDeclaredConstructor().newInstance();
            for (Map.Entry<String, ObjectWrapper> entry : topLevelMap.entrySet()) {
                newMap.put(entry.getKey(), unwrapObjectWrapperContent(entry.getValue()));
            }
            topLevelObject = newMap;
        } else if (topLevelObject instanceof List) {
            List<ObjectWrapper> listOfObjectWrappers = (List<ObjectWrapper>) topLevelObject;
            List<Object> newList = (List<Object>) Class.forName(topLevelWrapper.getClazz())
                    .getDeclaredConstructor().newInstance();
            for (ObjectWrapper objectWrapper : listOfObjectWrappers) {
                newList.add(unwrapObjectWrapperContent(objectWrapper));
            }
            topLevelObject = newList;
        }
        return topLevelObject;
    }
}

CustomHashMapJsonDeserializer类

@Log4j2
public class CustomHashMapJsonDeserializer extends JsonDeserializer<ObjectWrapper> {

    private final ObjectMapper objectMapper = getDefaultObjectMapper();

    private static ObjectMapper getDefaultObjectMapper() {
        ObjectMapper defaultObjectMapper = new ObjectMapper();
        defaultObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return defaultObjectMapper;
    }

    @Override
    public ObjectWrapper deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JacksonException {
        JsonNode node = p.getCodec().readTree(p);
        return convertNode(node);
    }

    private ObjectWrapper convertNode(JsonNode node)
            throws IOException, IllegalArgumentException {
        ObjectWrapper topLevelWrapper = objectMapper.readValue(objectMapper.treeAsTokens(node), ObjectWrapper.class);
        try {
            // TODO: Ugly workaround with mapper.treeToValue(mapper.valueToTree(object)) to
            // get back the JsonNode in order to properly deserialize the objects into their
            // respective types
            topLevelWrapper.setValue(objectMapper.treeToValue(objectMapper.valueToTree(topLevelWrapper.getValue()),
                    Class.forName(topLevelWrapper.getClazz())));
            if (topLevelWrapper.getValue() instanceof Map) {
                Map<String, Object> mapObject = (Map<String, Object>) topLevelWrapper.getValue();
                for (Map.Entry<String, Object> entry : mapObject.entrySet()) {
                    // TODO: Ugly workaround with mapper.valueToTree(object) to get back the
                    // JsonNode in order to properly deserialize the objects into their respective
                    // types
                    mapObject.put(entry.getKey(), convertNode(objectMapper.valueToTree(entry.getValue())));
                }
                topLevelWrapper.setValue(mapObject);
            } else if (topLevelWrapper.getValue() instanceof List) {
                List<Object> listObject = (List<Object>) topLevelWrapper.getValue();
                List<Object> listObjectCopy = (List<Object>) Class.forName(topLevelWrapper.getClazz())
                        .getDeclaredConstructor().newInstance();
                for (Object objectInList : listObject) {
                    // TODO: Ugly workaround with mapper.valueToTree(object) to get back the
                    // JsonNode in order to properly deserialize the objects into their respective
                    // types
                    listObjectCopy.add(convertNode(objectMapper.valueToTree(objectInList)));
                }
                topLevelWrapper.setValue(listObjectCopy);
                listObject = null;
            }
            // TODO: Too many damn exceptions... Isn't there a way how I can do all this
            // without so many possible Exceptions being thrown?
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            log.error("Error while deserializing JSON: ", e);
        }
        return topLevelWrapper;
    }
}

优化方案与实现

核心优化点

  1. 简化类型存储逻辑:替换不可靠的类名解析方式,直接用Class.getName()获取全限定类名
  2. 复用ObjectMapper与模块:避免每次反序列化都重复注册模块,提升性能
  3. 减少中间转换步骤:去掉反复转换JsonNode的冗余操作,直接通过自定义序列化/反序列化器处理嵌套结构
  4. 优化反射操作:缓存常用集合类的Class实例,减少反射开销

优化后的代码

ObjectWrapper类(简化字段名)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ObjectWrapper {
    private String type;
    private Object value;
}

整合式转换器(合并序列化/反序列化逻辑)

@Log4j2
public class NestedMapJsonConverter implements AttributeConverter<HashMap<String, Object>, String> {

    private static final ObjectMapper OBJECT_MAPPER = initObjectMapper();
    // 缓存常用集合类,避免重复反射
    private static final Map<String, Class<?>> CACHED_CLASSES = Map.of(
            HashMap.class.getName(), HashMap.class,
            ArrayList.class.getName(), ArrayList.class
    );

    private static ObjectMapper initObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        
        SimpleModule module = new SimpleModule();
        module.addSerializer(ObjectWrapper.class, new ObjectWrapperSerializer());
        module.addDeserializer(ObjectWrapper.class, new ObjectWrapperDeserializer());
        mapper.registerModule(module);
        
        return mapper;
    }

    @Override
    public String convertToDatabaseColumn(HashMap<String, Object> attributeMap) {
        try {
            ObjectWrapper wrapper = wrapNestedStructure(attributeMap);
            // 生产环境去掉格式化,减少JSON体积和序列化时间
            return OBJECT_MAPPER.writeValueAsString(wrapper);
        } catch (JsonProcessingException e) {
            log.error("序列化嵌套Map失败", e);
            return null;
        }
    }

    @Override
    public HashMap<String, Object> convertToEntityAttribute(String dbJsonData) {
        if (dbJsonData == null || dbJsonData.isEmpty()) {
            return new HashMap<>();
        }
        try {
            ObjectWrapper wrapper = OBJECT_MAPPER.readValue(dbJsonData, ObjectWrapper.class);
            return (HashMap<String, Object>) unwrapNestedStructure(wrapper);
        } catch (Exception e) {
            log.error("反序列化JSON失败", e);
            return new HashMap<>();
        }
    }

    private ObjectWrapper wrapNestedStructure(Object obj) {
        if (obj instanceof Map) {
            Map<String, Object> wrappedMap = new HashMap<>();
            for (Map.Entry<String, Object> entry : ((Map<String, Object>) obj).entrySet()) {
                wrappedMap.put(entry.getKey(), wrapNestedStructure(entry.getValue()));
            }
            return new ObjectWrapper(obj.getClass().getName(), wrappedMap);
        } else if (obj instanceof List) {
            List<Object> wrappedList = new ArrayList<>();
            for (Object item : ((List<Object>) obj)) {
                wrappedList.add(wrapNestedStructure(item));
            }
            return new ObjectWrapper(obj.getClass().getName(), wrappedList);
        } else {
            return new ObjectWrapper(obj.getClass().getName(), obj);
        }
    }

    private Object unwrapNestedStructure(ObjectWrapper wrapper) throws Exception {
        String type = wrapper.getType();
        Class<?> clazz = CACHED_CLASSES.getOrDefault(type, Class.forName(type));
        Object value = wrapper.getValue();
        
        if (Map.class.isAssignableFrom(clazz)) {
            Map<String, Object> resultMap = (Map<String, Object>) clazz.getDeclaredConstructor().newInstance();
            for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) {
                resultMap.put(entry.getKey(), unwrapNestedStructure((ObjectWrapper) entry.getValue()));
            }
            return resultMap;
        } else if (List.class.isAssignableFrom(clazz)) {
            List<Object> resultList = (List<Object>) clazz.getDeclaredConstructor().newInstance();
            for (Object item : ((List<Object>) value)) {
                resultList.add(unwrapNestedStructure((ObjectWrapper) item));
            }
            return resultList;
        } else {
            return clazz.cast(value);
        }
    }

    // 自定义序列化器,直接写入ObjectWrapper字段
    static class ObjectWrapperSerializer extends JsonSerializer<ObjectWrapper> {
        @Override
        public void serialize(ObjectWrapper value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeStartObject();
            gen.writeStringField("type", value.getType());
            gen.writeObjectField("value", value.getValue());
            gen.writeEndObject();
        }
    }

    // 自定义反序列化器,直接解析字段并转换类型
    static class ObjectWrapperDeserializer extends JsonDeserializer<ObjectWrapper> {
        @Override
        public ObjectWrapper deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            JsonNode node = p.getCodec().readTree(p);
            String type = node.get("type").asText();
            JsonNode valueNode = node.get("value");
            
            try {
                Class<?> clazz = CACHED_CLASSES.getOrDefault(type, Class.forName(type));
                Object value;
                if (Map.class.isAssignableFrom(clazz)) {
                    value = ctxt.readValue(valueNode.traverse(), Map.class);
                } else if (List.class.isAssignableFrom(clazz)) {
                    value = ctxt.readValue(valueNode.traverse(), List.class);
                } else {
                    value = ctxt.readValue(valueNode.traverse(), clazz);
                }
                return new ObjectWrapper(type, value);
            } catch (ClassNotFoundException e) {
                throw new IOException("无法找到指定类型: " + type, e);
            }
        }
    }
}

额外建议

  • 如果不需要严格保留原Map/List的实现类(比如只需要兼容Map接口),可以直接用LinkedHashMap/ArrayList代替反射创建实例,进一步提升性能
  • 可以添加类型校验逻辑,避免非法类型被序列化到数据库中
  • 针对高频访问的配置数据,可以添加本地缓存,减少数据库读取和反序列化次数

内容的提问来源于stack exchange,提问作者Browbeat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 11:54:56