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; });
}

之前的代码因MapUser对象之间的类型不匹配而失败。

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上找到。