前言

本文提供三种 Java 数据处理核心方案,涵盖对象与 Map 的双向映射及复杂 Map 结构扁平化,适用于接口参数转换、数据同步、跨系统数据传输等场景:

  • 注解式映射(推荐):通过自定义注解标记字段映射关系,侵入性低、配置直观,适配复杂嵌套对象
  • KV 配置式映射:基于 Hutool 工具类,通过 Map 配置字段对应关系,适合简单对象快速转换
  • Map 扁平化工具:将嵌套 Map/List 结构转为单层键值对,适配 URL 参数拼接、简单配置输出等场景

其中注解式映射因无需额外配置类、可读性强、支持多层嵌套,更推荐作为优先方案。

方案一:注解式对象 - Map 映射(推荐)

通过自定义 @Table@Table.Col 注解,直接在 JavaBean 字段上标记目标映射键名,配合工具类实现对象与 Map 的双向递归转换,适配含嵌套对象、集合的复杂场景。

核心注解定义(@Table + @Table.Col

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.landray.kmss.shunan.travel.annotations;

import java.lang.annotation.*;

/**
* 类级注解:标记该类支持字段映射功能(用于对象-Map双向转换)
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
/**
* 字段级注解:指定当前字段对应的目标键名(支持数据库列名、接口参数名等场景)
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Col {
String value(); // 目标映射键名(如接口参数名、数据库列名)
}
}

注解使用示例(出差申请单数据模型)

以出差申请单同步接口的请求参数模型为例,通过注解标记每个字段对应的目标键名,支持嵌套对象(如SuperiorDepartment)、集合(如List<PassengerDetail>)的多层映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.landray.kmss.shunan.travel.sync.data;

import com.landray.kmss.shunan.travel.annotations.Table;

import java.util.List;

/**
* 出差申请单同步接口(FCAPPLY_OUTAPI_TripModify)的请求参数数据类
* 注:仅标注文档明确标“Y”的必填项,非标注项按文档描述为准
*/
@Table // 类级注解:标记该类支持映射转换
public class TripApplyData {
// ====================== 文档明确标注“Y”的显式必填项 ======================
/**
* 出差申请单号,必填(文档明确标“Y”)
* 对应外系统申请单号
*/
@Table.Col("fd_approval_number") // 字段级注解:映射目标键名
private String approvalNumber;

/**
* 操作类型,必填(文档明确标“Y”)
* 枚举:0=更新,1=新增,2=作废,3=修改状态,4=校验是否行程重复
* 默认值:0
*/
@Table.Col("fd_op_code")
private int opCode = 0;

// ... 中间字段省略(保持原代码结构) ...

/**
* 上级部门集合,非必填(文档标“N”)
* 元素类型:SuperiorDepartment(上级部门信息)- 嵌套对象同样支持注解映射
*/
@Table.Col("fd_superior_depart_list")
private List<SuperiorDepartment> superiorDepartList;

// ====================== 嵌套内部类(均支持注解映射) ======================

/**
* 上级部门信息(对应superiorDepartList)
* 注:所有字段均为非必填(文档标“N”)
*/
@Table // 嵌套类同样需要添加@Table注解
public static class SuperiorDepartment {
/**
* 上级部门编号,非必填(文档标“N”)
* 说明:按顺序传入,顶级部门位于第一个
*/
@Table.Col("fd_dept_id")
private String deptId;

// ... 其他字段及getter/setter省略 ...
}

/**
* 出行人详情(对应passengerList)
* 注:仅标注文档明确“Y”的必填项
*/
@Table
public static class PassengerDetail {
/**
* 姓名,必填(文档明确标“Y”)
* 支持:数字、大小写字母、中文、右斜杠/、空格
*/
@Table.Col("fd_name")
private String name;

// ... 其他字段及getter/setter省略 ...
}

// ... 其他内部类及全局getter/setter省略 ...
}

注解映射核心工具类(BeanMapMapper

基于 Hutool 反射工具实现,支持:

  • 对象 → Map:递归转换嵌套对象、集合(集合元素需标注@Table
  • Map → 对象:根据映射关系反向构建对象,自动处理嵌套结构和集合类型
  • 空值过滤:忽略值为 null 的字段,避免无效数据传输
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.landray.kmss.shunan.travel.util;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReflectUtil;
import com.landray.kmss.shunan.travel.annotations.Table;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
* 注解式对象-Map双向转换工具类
* 依赖:Hutool反射工具(ReflectUtil)、空值判断工具(ObjUtil)
* 核心功能:基于@Table@Table.Col注解,实现复杂对象与Map的递归转换
*/
public final class BeanMapMapper {

// 私有构造器:禁止实例化工具类
private BeanMapMapper() {}

/* ==================== 1. 实体对象 → Map(支持嵌套/集合) ==================== */
/**
* 将标注@Table的实体对象转为Map,字段名对应@Table.Col的value值
* @param bean 待转换的实体对象(必须标注@Table注解)
* @return 转换后的Map(空对象/未标注注解返回null)
*/
public static Map<String, Object> toMap(Object bean) {
if (ObjUtil.isNull(bean)) return null;
Class<?> clazz = bean.getClass();
if (!clazz.isAnnotationPresent(Table.class)) return null; // 未标注@Table直接返回null

Map<String, Object> map = new LinkedHashMap<>(); // 保持字段顺序
Field[] fields = ReflectUtil.getFields(clazz); // 获取所有字段(含私有)
for (Field f : fields) {
Table.Col col = f.getAnnotation(Table.Col.class);
if (ObjUtil.isNull(col)) continue; // 未标注@Table.Col的字段忽略
Object val = ReflectUtil.getFieldValue(bean, f);
if (ObjUtil.isNull(val)) continue; // 空值字段忽略

// 处理集合类型(List/Set):递归转换集合中的@Table标注对象
if (Collection.class.isAssignableFrom(f.getType())) {
Type generic = f.getGenericType();
if (generic instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) generic;
Class<?> actual = (Class<?>) pt.getActualTypeArguments()[0];
if (actual.isAnnotationPresent(Table.class)) { // 集合元素是标注@Table的对象
List<Map<String, Object>> list = new ArrayList<>();
for (Object item : (Collection<?>) val) list.add(toMap(item));
map.put(col.value(), list);
continue;
}
}
}
// 处理嵌套对象:递归转换标注@Table的字段
if (f.getType().isAnnotationPresent(Table.class)) {
map.put(col.value(), toMap(val));
continue;
}
// 普通字段:直接存入Map
map.put(col.value(), val);
}
return map;
}

/* ==================== 2. Map → 实体对象(支持嵌套/集合) ==================== */
/**
* 将Map转为指定类型的实体对象,Map的key对应@Table.Col的value值
* @param map 待转换的Map
* @param clazz 目标实体类(必须标注@Table注解)
* @return 转换后的实体对象(空Map/未标注注解返回null)
*/
@SuppressWarnings("unchecked")
public static <T> T toBean(Map<String, Object> map, Class<T> clazz) {
if (MapUtil.isEmpty(map)) return null;
if (!clazz.isAnnotationPresent(Table.class)) return null; // 未标注@Table直接返回null

T instance = ReflectUtil.newInstance(clazz); // 反射创建对象实例
Field[] fields = ReflectUtil.getFields(clazz);
for (Field f : fields) {
Table.Col col = f.getAnnotation(Table.Col.class);
if (ObjUtil.isNull(col)) continue; // 未标注@Table.Col的字段忽略
Object val = map.get(col.value());
if (ObjUtil.isNull(val)) continue; // 空值字段忽略

// 处理嵌套对象:递归转换为目标字段类型
if (f.getType().isAnnotationPresent(Table.class)) {
ReflectUtil.setFieldValue(instance, f, toBean((Map<String, Object>) val, f.getType()));
continue;
}
// 处理集合类型:递归转换集合元素为目标类型
if (Collection.class.isAssignableFrom(f.getType())) {
Type generic = f.getGenericType();
if (generic instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) generic;
Class<?> actual = (Class<?>) pt.getActualTypeArguments()[0];
if (actual.isAnnotationPresent(Table.class) && val instanceof List) {
List<Map<String, Object>> list = (List<Map<String, Object>>) val;
Collection<Object> target = createCollection(f.getType()); // 创建对应集合实例
for (Map<String, Object> item : list) target.add(toBean(item, actual));
ReflectUtil.setFieldValue(instance, f, target);
continue;
}
}
}
// 普通字段:直接设置值(Hutool自动处理类型转换)
ReflectUtil.setFieldValue(instance, f, val);
}
return instance;
}

/* ==================== 辅助方法:创建集合实例 ==================== */
/**
* 根据字段类型创建对应的Collection实例(默认List/Set实现)
* @param fieldType 集合字段类型(List/Set)
* @return 对应的Collection实例
*/
private static Collection<Object> createCollection(Class<?> fieldType) {
if (List.class.isAssignableFrom(fieldType)) return new ArrayList<>();
if (Set.class.isAssignableFrom(fieldType)) return new LinkedHashSet<>(); // 保持插入顺序
return new ArrayList<>(); // 默认返回ArrayList
}
}

工具类调用示例

从模型数据中获取 Map,通过 BeanMapMapper 快速转换为目标实体类,适配数据同步场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 从模型中获取原始Map数据
* @return 模型数据Map
*/
private Map<String, Object> getModelData() {
try {
return kmReviewMain.getExtendDataModelInfo().getModelData();
} catch (Exception e) {
throw new SyncBusinessException("MODEL_DATA_ERROR", "获取模型数据异常", e);
}
}

/**
* 将Map数据转换为出差申请单实体类
* @return 转换后的TripApplyData实例
*/
private TripApplyData getApply() {
try {
// 核心调用:Map → 实体
return BeanMapMapper.toBean(getModelData(), TripApplyData.class);
} catch (Exception e) {
throw new SyncBusinessException("MODEL_DATA_ERROR", "Map转出差申请单实体异常", e);
}

// 若需实体转Map,直接调用:
// TripApplyData applyData = new TripApplyData();
// Map<String, Object> applyMap = BeanMapMapper.toMap(applyData);
}

方案二:KV 配置式对象转换(基于 Hutool

通过 Map 配置字段映射关系,结合 HutoolBeanUtil 实现对象之间的快速转换,适用于字段较少、映射关系简单的场景(如 VO 与数据库实体的转换)。

转换工具类(SyncBeanConvertUtil

封装对象转换逻辑,通过预定义的映射配置(CITY_VO_COPY_OPTIONS)实现 VO 到实体的批量转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.landray.kmss.shunan.travel.sync.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.landray.kmss.shunan.travel.model.ShunanTravelCity;
import com.landray.kmss.shunan.travel.sync.vo.CityVO;

import java.util.ArrayList;
import java.util.List;

import static com.landray.kmss.shunan.travel.mappings.ShunanTravelCityVoMapping.CITY_VO_COPY_OPTIONS;

/**
* VO与数据库实体转换工具类
* 核心依赖:Hutool的BeanUtil(字段拷贝)、CollUtil(集合判空)
* 适用场景:简单对象(无复杂嵌套)的快速转换
*/
public class SyncBeanConvertUtil {

/**
* 将城市VO列表转换为数据库实体列表
* @param cityVO 城市接口返回的VO(含数据列表)
* @return 转换后的ShunanTravelCity实体列表(空输入返回空列表)
*/
public static List<ShunanTravelCity> cityVoToEntityList(CityVO cityVO) {
// 空值校验:避免空指针异常
if (ObjUtil.isNull(cityVO) || CollUtil.isEmpty(cityVO.getDataList())) {
return new ArrayList<>();
}

List<ShunanTravelCity> entityList = new ArrayList<>();
// 遍历VO列表,逐个转换为实体
for (CityVO.QueryCityCommonBean voBean : cityVO.getDataList()) {
ShunanTravelCity entity = new ShunanTravelCity();
// 核心转换:基于KV映射配置拷贝字段
BeanUtil.copyProperties(voBean, entity, CITY_VO_COPY_OPTIONS);
entityList.add(entity);
}

return entityList;
}
}

KV 映射配置类(ShunanTravelCityVoMapping

通过 Map 定义 VO 字段与实体字段的映射关系,配合 HutoolCopyOptions实现字段别名映射,配置直观、便于维护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.landray.kmss.shunan.travel.mappings;

import cn.hutool.core.bean.copier.CopyOptions;
import java.util.HashMap;
import java.util.Map;

/**
* 城市VO与数据库实体的KV映射配置类
* 作用:定义VO字段名 → 实体字段名的映射关系,供BeanUtil拷贝使用
*/
public class ShunanTravelCityVoMapping {

/**
* KV映射关系:key=VO字段名,value=实体字段名
*/
public static final Map<String, String> CITY_VO_TO_ENTITY_MAPPING;

/**
* Hutool拷贝配置:包含KV映射关系,用于BeanUtil.copyProperties
*/
public static final CopyOptions CITY_VO_COPY_OPTIONS;

// 静态代码块:初始化映射关系和拷贝配置
static {
CITY_VO_TO_ENTITY_MAPPING = new HashMap<>();

// 逐个配置字段映射关系
CITY_VO_TO_ENTITY_MAPPING.put("code", "fdCode");
CITY_VO_TO_ENTITY_MAPPING.put("name", "fdName");
CITY_VO_TO_ENTITY_MAPPING.put("englishName", "fdEnglishName");
CITY_VO_TO_ENTITY_MAPPING.put("countrySimpleCode", "fdCountrySimpleCode");
CITY_VO_TO_ENTITY_MAPPING.put("type", "fdType");
CITY_VO_TO_ENTITY_MAPPING.put("countryBelongCode", "fdCountryBelongCode");
CITY_VO_TO_ENTITY_MAPPING.put("countryBelongNameZh", "fdCountryBelongNameZh");
CITY_VO_TO_ENTITY_MAPPING.put("provinceBelongCode", "fdProvinceBelongCode");
CITY_VO_TO_ENTITY_MAPPING.put("provinceBelongNameZh", "fdProvinceBelongNameZh");
CITY_VO_TO_ENTITY_MAPPING.put("cityInitial", "fdCityinitial");
CITY_VO_TO_ENTITY_MAPPING.put("parentCityCode", "fdParentCityCode");
CITY_VO_TO_ENTITY_MAPPING.put("cityBelongCode", "fdCityBelongCode");
CITY_VO_TO_ENTITY_MAPPING.put("cityBelongNameZh", "fdCityBelongNameZh");
CITY_VO_TO_ENTITY_MAPPING.put("isDomestic", "fdIsDomestic");
CITY_VO_TO_ENTITY_MAPPING.put("haveAirport", "fdHaveAirport");
CITY_VO_TO_ENTITY_MAPPING.put("haveTrainStation", "fdHaveTrainStation");
CITY_VO_TO_ENTITY_MAPPING.put("nationalAdministrativeCode", "fdNationalAdministrativeCode");

// 初始化拷贝配置:绑定KV映射关系
CITY_VO_COPY_OPTIONS = new CopyOptions().setFieldMapping(CITY_VO_TO_ENTITY_MAPPING);
}

// 私有构造器:禁止实例化配置类
private ShunanTravelCityVoMapping() {}
}

转换工具调用示例

在接口数据同步场景中,将接口返回的 VO 快速转换为数据库实体,用于后续持久化操作:

1
2
3
4
5
6
7
8
9
10

/**
* 城市数据同步示例:VO → 实体
*/
// 1. 从接口获取返回结果(假设apiResponse为接口响应对象)
CityVO cityVO = apiResponse.getResult();

// 2. 调用工具类转换为实体列表
List<ShunanTravelCity> currentPageCities = SyncBeanConvertUtil.cityVoToEntityList(cityVO);

实用工具:Map 结构扁平化工具类(MapUtil

扩展 HutoolMapUtil,提供嵌套 Map/List 结构的扁平化功能,将多层级键值对转为单层结构,适用于 URL 参数拼接、简单配置输出、日志打印等场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.landray.kmss.shunan.travel.util;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;

import java.util.List;
import java.util.Map;

/**
* Hutool MapUtil扩展工具类:提供嵌套Map/List的扁平化功能
* 核心场景:
* 1. 嵌套Map转URL查询参数(如:user.name=张三&hobbies[0]=编程)
* 2. 复杂Map结构日志打印(单层结构更易读)
* 3. 简单配置文件输出(需单层键值对格式)
*/
public class MapUtil extends cn.hutool.core.map.MapUtil {

/**
* 递归扁平化嵌套Map/List结构
* @param sourceMap 待扁平化的源Map(支持值为Map、List或普通对象)
* @param parentKey 父层级键名(顶层调用传空字符串"")
* @param targetMap 接收结果的目标Map(需提前创建,如new HashMap<>())
*/
public static void flattenMap(Map<String, Object> sourceMap, String parentKey, Map<String, String> targetMap) {
// 健壮性检查:避免空指针
if (isEmpty(sourceMap) || ObjUtil.isNull(targetMap)) {
return;
}

// 遍历源Map的每个键值对
for (Map.Entry<String, Object> entry : sourceMap.entrySet()) {
String currentKey = entry.getKey();
Object currentValue = entry.getValue();

// 拼接完整键名:父键名 + 当前键名(顶层无父键名直接用当前键名)
String fullKey = StrUtil.isEmpty(parentKey) ? currentKey : parentKey + "." + currentKey;

// 过滤空值:不加入结果Map
if (ObjUtil.isNull(currentValue)) {
continue;
}

// 递归处理嵌套Map(如:{user: {name: 张三}} → user.name=张三)
if (currentValue instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> nestedMap = (Map<String, Object>) currentValue;
flattenMap(nestedMap, fullKey, targetMap);
}
// 处理List/数组(如:{hobbies: [编程, 阅读]} → hobbies[0]=编程&hobbies[1]=阅读)
else if (currentValue instanceof List) {
List<?> list = (List<?>) currentValue;
for (int i = 0; i < list.size(); i++) {
Object listItem = list.get(i);
String listKey = fullKey + "[" + i + "]"; // 列表元素键名:键名[索引]
// 列表元素为Map时继续递归(如:{passengers: [{name: 张三}]} → passengers[0].name=张三)
if (listItem instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> itemMap = (Map<String, Object>) listItem;
flattenMap(itemMap, listKey, targetMap);
} else {
// 普通列表元素:直接转为字符串存入结果
targetMap.put(listKey, StrUtil.toString(listItem));
}
}
}
// 普通字段:直接转为字符串存入结果(兼容所有基本类型)
else {
targetMap.put(fullKey, StrUtil.toString(currentValue));
}
}
}
}

扁平化效果示例

源嵌套 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"user": {
"name": "张三",
"age": 30
},
"hobbies": ["编程", "阅读"],
"address": {
"city": "北京",
"detail": "XX街道"
},
"passengers": [
{"name": "李四", "phone": "123456"},
{"name": "王五", "phone": "654321"}
]
}

扁平化后 Map

1
2
3
4
5
6
7
8
9
10
11
12
{
"user.name": "张三",
"user.age": "30",
"hobbies[0]": "编程",
"hobbies[1]": "阅读",
"address.city": "北京",
"address.detail": "XX街道",
"passengers[0].name": "李四",
"passengers[0].phone": "123456",
"passengers[1].name": "王五",
"passengers[1].phone": "654321"
}

调用示例

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 准备源嵌套Map(可通过BeanMapMapper.toMap(实体)获取)
Map<String, Object> sourceMap = new HashMap<>();
// ... 向sourceMap添加嵌套数据 ...

// 2. 创建目标Map接收结果
Map<String, String> flatMap = new HashMap<>();

// 3. 调用扁平化方法(顶层parentKey传"")
MapUtil.flattenMap(sourceMap, "", flatMap);

// 4. 后续使用(如转为URL参数)
// String urlParams = HttpUtil.toParams(flatMap);

方案对比与选择建议

方案类型 核心优势 适用场景 依赖工具
注解式映射 侵入性低、支持复杂嵌套、配置直观 字段多、嵌套深的对象(如接口参数) Hutool 反射工具
KV 配置式映射 配置集中、无需修改实体类 简单对象(如 VO 与数据库实体转换) Hutool BeanUtil
Map 扁平化工具 解决多层结构转单层需求 URL 参数、日志打印、简单配置输出 Hutool 基础工具

优先选择注解式映射: 对于大多数业务场景(尤其是接口数据同步、复杂参数转换),注解式映射的灵活性和可读性更优;仅在无需修改实体类(如第三方提供的 VO)或字段极少时,考虑 KV 配置式映射。