1. 概述

在本教程中,我们将介绍如何使用 Jackson 和 Gson 将不同格式的 JSON 数据映射到同一个 Java 字段上。这在对接多个第三方 API 时特别有用,尤其是当它们返回结构相似但字段命名不一致的数据时。

2. Maven 依赖

为了使用 JacksonGson 库,我们需要在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.0</version>
    <scope>test</scope>
</dependency>

⚠️ 注意:这里我们将依赖范围设置为 test,因为示例代码主要用于单元测试场景。

3. 示例 JSON 数据

假设我们要从不同的天气服务接口获取数据,并统一处理。这两个服务返回的 JSON 格式略有不同:

{
    "location": "London",
    "temp": 15,
    "weather": "Cloudy"
}

和:

{
    "place": "Lisbon",
    "temperature": 35,
    "outlook": "Sunny"
}

我们的目标是将这两种格式都反序列化为同一个 Java 类 —— Weather

public class Weather {
    private String location;
    private int temp;
    private String outlook;
}

接下来我们看看如何分别使用 Jackson 和 Gson 实现这一需求。

4. 使用 Jackson 实现映射

核心思路:通过 @JsonProperty@JsonAlias 注解实现多字段映射。

  • @JsonProperty:用于指定主字段名(用于序列化和反序列化)
  • @JsonAlias:用于指定别名字段(仅用于反序列化)

示例代码如下:

@JsonProperty("location")
@JsonAlias("place")
private String location;

@JsonProperty("temp")
@JsonAlias("temperature")
private int temp;

@JsonProperty("outlook")
@JsonAlias("weather")
private String outlook;

然后我们使用 Jackson 的 ObjectMapper 来测试反序列化逻辑:

@Test
public void givenTwoJsonFormats_whenDeserialized_thenWeatherObjectsCreated() throws Exception {

    ObjectMapper mapper = new ObjectMapper();

    Weather weather = mapper.readValue("{\n"  
      + "  \"location\": \"London\",\n" 
      + "  \"temp\": 15,\n" 
      + "  \"weather\": \"Cloudy\"\n" 
      + "}", Weather.class);

    assertEquals("London", weather.getLocation());
    assertEquals("Cloudy", weather.getOutlook());
    assertEquals(15, weather.getTemp());

    weather = mapper.readValue("{\n" 
      + "  \"place\": \"Lisbon\",\n" 
      + "  \"temperature\": 35,\n"
      + "  \"outlook\": \"Sunny\"\n"
      + "}", Weather.class);

    assertEquals("Lisbon", weather.getLocation());
    assertEquals("Sunny", weather.getOutlook());
    assertEquals(35, weather.getTemp());
}

✅ 踩坑提示:如果你只用了 @JsonAlias 而没有 @JsonProperty,序列化时会找不到对应字段哦!

5. 使用 Gson 实现映射

核心思路:使用 @SerializedName 注解中的 valuealternate 参数。

  • value:默认字段名
  • alternate:可选字段别名(支持多个)

示例代码如下:

@SerializedName(value="location", alternate="place")
private String location;

@SerializedName(value="temp", alternate="temperature")
private int temp;

@SerializedName(value="outlook", alternate="weather")
private String outlook;

测试代码如下:

@Test
public void givenTwoJsonFormats_whenDeserialized_thenWeatherObjectsCreated() throws Exception {
        
    Gson gson = new GsonBuilder().create();
    Weather weather = gson.fromJson("{\n" 
      + "  \"location\": \"London\",\n" 
      + "  \"temp\": 15,\n" 
      + "  \"weather\": \"Cloudy\"\n" 
      + "}", Weather.class);
        
    assertEquals("London", weather.getLocation());
    assertEquals("Cloudy", weather.getOutlook());
    assertEquals(15, weather.getTemp());
        
    weather = gson.fromJson("{\n"
      + "  \"place\": \"Lisbon\",\n"
      + "  \"temperature\": 35,\n"
      + "  \"outlook\": \"Sunny\"\n"
      + "}", Weather.class);
       
    assertEquals("Lisbon", weather.getLocation());
    assertEquals("Sunny", weather.getOutlook());
    assertEquals(35, weather.getTemp());
        
}

✅ 小贴士:Gson 的 alternate 支持传入数组,比如 alternate={"place", "address"},适合字段别名较多的场景。

6. 总结

在这篇文章中我们展示了两种主流 JSON 解析库的多字段映射方案:

主字段注解 别名字段注解
Jackson @JsonProperty @JsonAlias
Gson @SerializedName alternate 属性

无论你是对接多个第三方接口,还是处理历史遗留的多种数据格式,这两种方式都能让你优雅地统一数据模型,避免写一堆适配器类。

📌 完整代码可参考 GitHub 仓库:


原始标题:Mapping Multiple JSON Fields to One Java Field