1. 概述
直接将Map
对象转换为复杂对象(如POJOs)是一种强大的技术,它允许我们利用类型安全的优势来操作现有数据。本教程将详细介绍这三种方法:如何使用Jackson、Gson和BeanUtils API进行转换。
2. 域类
我们将通过一个用户(User)及其多个地址(Address)的示例来展示不同库的功能。每个地址都关联着一个国家(Country):
class User {
private Long id;
private String name;
private List<Address> addresses;
// standard getters and setters
}
Address
类表示用户的地址,包括城市(city)和国家(country):
class Address {
private String city;
private Country country;
// standard getters and setters
}
Country
类表示地址关联的国家:
class Country {
private String name;
// standard getters and setters
}
我们将使用这个Map
对象来演示如何用不同的库将数据转换为User
对象:
private static final Map<String, Object> map = Map.of(
"id", 1L,
"name", "Baeldung",
"addresses", List.of(
new Address("La Havana", new Country("Cuba")),
new Address("Paris", new Country("France"))
)
);
为了避免重复,并确保在验证转换后的User
对象与原始Map
的一致性,我们将使用一个辅助方法:
private static void assertEqualsMapAndUser(Map<String, Object> map, User user) {
assertEquals(map.get("id"), user.getId());
assertEquals(map.get("name"), user.getName());
assertEquals(map.get("addresses"), user.getAddresses());
}
现在,有了域类和辅助方法,我们准备好探索这些库的能力。
3. 为什么直接转换不可行?
由于类型不兼容问题,直接将Map
转换为对象是不可行的。这会导致ClassCastException
:
@Test
void givenMap_whenCasting_thenThrow() {
assertThrows(ClassCastException.class, () -> { User user = (User) map; });
}
之前的代码因Map
和User
对象之间的类型不匹配而失败。
4. 使用Jackson
Jackson是一个灵活的库,擅长将对象转换为各种序列化格式,包括Map
对象。在pom.xml
文件中添加jackson-databind依赖即可:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
Jackson的核心功能由ObjectMapper
类提供,它是Jackson在不同格式之间转换数据的主要工具。
convertValue()
方法将Map
转换为User
对象,其中Map
中的每个键成为结果对象的字段名,相应值填充该字段:
@Test
void givenMap_whenUsingJackson_thenConvertToObject() {
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.convertValue(map, User.class);
assertEqualsMapAndUser(map, user);
}
如果Map
包含与目标类属性不匹配的键,Jackson默认会抛出异常:
@Test
void givenMap_whenUsingJacksonWithWrongAttrs_thenThrow() {
Map<String, Object> modifiedMap = new HashMap<>(map);
modifiedMap.put("enabled", true);
ObjectMapper objectMapper = new ObjectMapper();
assertThrows(IllegalArgumentException.class, () -> objectMapper.convertValue(modifiedMap, User.class));
}
我们可以配置Jackson来忽略未知属性:
@Test
void givenMap_whenUsingJacksonIgnoreUnknownProps_thenConvertToObject() {
Map<String, Object> modifiedMap = new HashMap<>(map);
modifiedMap.put("enabled", true);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
User user = objectMapper.convertValue(modifiedMap, User.class);
assertEqualsMapAndUser(modifiedMap, user);
}
这次,未知属性被忽略,其余的Map
转换成了预期的User
对象。
5. 使用Gson
Gson是一个易于使用的库,专注于将对象转换为JSON格式。它简单易用,适用于主要处理JSON数据交换的项目。
在pom.xml
文件中添加gson依赖:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
与Jackson不同,Gson需要两步操作。首先,使用toJson()
将Map
转换为JSON字符串,然后使用fromJson()
将结果JSON反序列化为User
对象:
@Test
void givenMap_whenUsingGson_thenConvertToObject() {
Gson gson = new Gson();
String jsonMap = gson.toJson(map);
User user = gson.fromJson(jsonMap, User.class);
assertEqualsMapAndUser(map, user);
}
Gson无需配置就能处理Map
中的未知属性,它们会在转换过程中被无缝忽略。
6. 使用Apache Commons BeanUtils
对于更传统的Java Bean操作,Apache Commons BeanUtils提供了更好的选择。它特别适合依赖大量Java Bean的代码库。
在pom.xml
文件中添加beanutils依赖:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
这个库提供了populate()
方法,可以直接使用Map
填充对象的字段:
@Test
void givenMap_whenUsingBeanUtils_thenConvertToObject() throws InvocationTargetException, IllegalAccessException {
User user = new User();
BeanUtils.populate(user, map);
assertEqualsMapAndUser(map, user);
}
与Jackson一样,BeanUtils在转换时也会忽略Map
中的未知属性。
7. 比较
下表列出了三种方法的对比。这有助于我们在应用程序中选择最合适的选项将Map
转换为对象:
特性 | Jackson | Gson | BeanUtils |
---|---|---|---|
介绍 | 数据转换的灵活库 | 用户友好的数据转换 | 传统Java Bean操作方法 |
转换风格 | 直接转换 | 两步(Map -> JSON -> 对象) | 直接转换 |
灵活性 | 支持嵌套对象 | 支持嵌套对象 | 支持嵌套对象 |
社区 | 大且活跃 | 大且活跃 | 较小的社区 |
8. 总结
本文介绍了Java中将Map
对象转换为复杂类型(如POJOs)的三种方法:Jackson、Gson和Apache Commons BeanUtils。Jackson以其庞大的社区和强大的直接转换能力脱颖而出;Gson则因其简洁的用户体验和两步转换过程而受欢迎;BeanUtils适合处理依赖Java Bean的旧代码,其直接填充方法也具有一定的优势。源代码可以在GitHub上找到。