Contents

Gson 和 Jackson

1. 简介

在本文中,我们将比较GsonJackson API 用于将 JSON 数据序列化和反序列化为 Java 对象,反之亦然。 Gson 和 Jackson 是为 Java 提供 JSON 数据绑定支持的完整库。每个都是积极开发的开源项目,可以处理复杂的数据类型并支持 Java 泛型。 在大多数情况下,这两个库都可以在不修改实体类的情况下反序列化为实体,这在开发人员无权访问实体源代码的情况下很重要。

2. Gson Maven 依赖

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>${gson.version}</version>
</dependency>

您可以在此处 获取最新版本的 Gson 。

3. Gson序列化

序列化将 Java 对象转换为 JSON 输出。考虑以下实体:

public class ActorGson {
    private String imdbId;
    private Date dateOfBirth;
    private List<String> filmography;
    
    // getters and setters, default constructor and field constructor omitted
}
public class Movie {
    private String imdbId;
    private String director;
    private List<ActorGson> actors;
    
    // getters and setters, default constructor and field constructor omitted
}

3.1.简单序列化

让我们从一个 Java 到 JSON 序列化的例子开始:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
ActorGson rudyYoungblood = new ActorGson(
  "nm2199632",
  sdf.parse("21-09-1982"), 
  Arrays.asList("Apocalypto",
  "Beatdown", "Wind Walkers")
);
Movie movie = new Movie(
  "tt0472043", 
  "Mel Gibson",
  Arrays.asList(rudyYoungblood));
String serializedMovie = new Gson().toJson(movie);

这将导致:

{
    "imdbId": "tt0472043",
    "director": "Mel Gibson",
    "actors": [{
        "imdbId": "nm2199632",
        "dateOfBirth": "Sep 21, 1982 12:00:00 AM",
        "filmography": ["Apocalypto", "Beatdown", "Wind Walkers"]
    }]
}

默认:

  • 所有属性都被序列化,因为它们没有
  • dateOfBirth字段已使用默认 Gson 日期模式进行翻译
  • 输出未格式化且 JSON 属性名称对应于 Java 实体

3.2. 自定义序列化

使用自定义序列化程序允许我们修改标准行为。我们可以使用 HTML 引入输出格式化程序、处理值、从输出中排除属性或添加新输出。 ActorGsonSerializer修改了ActorGson元素的 JSON 代码的生成:

public class ActorGsonSerializer implements JsonSerializer<ActorGson> {
    private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
     
    @Override
    public JsonElement serialize(ActorGson actor, Type type,
        JsonSerializationContext jsonSerializationContext) {
        
        JsonObject actorJsonObj = new JsonObject();
        
        actorJsonObj.addProperty("<strong>IMDB Code</strong>", actor.getImdbId());
        
        actorJsonObj.addProperty("<strong>Date Of Birth</strong>", 
          actor.getDateOfBirth() != null ? 
          sdf.format(actor.getDateOfBirth()) : null);
        
        actorJsonObj.addProperty("<strong>N° Film:</strong> ",  
          actor.getFilmography()  != null ?  
          actor.getFilmography().size() : null);
       
        actorJsonObj.addProperty("filmography", actor.getFilmography() != null ? 
          convertFilmography(actor.getFilmography()) : null);
        
        return actorJsonObj;
    }
 
    private String convertFilmography(List<String> filmography) {
        return filmography.stream()
          .collect(Collectors.joining("-"));
    }
}

为了排除director属性,@Expose注解用于我们要考虑的属性:

public class MovieWithNullValue {
    
    @Expose
    private String imdbId;
    private String director;
    
    @Expose
    private List<ActorGson> actors;
}

现在我们可以使用GsonBuilder类继续创建 Gson 对象:

Gson gson = new GsonBuilder()
  .setPrettyPrinting()
  .excludeFieldsWithoutExposeAnnotation()
  .serializeNulls()
  .disableHtmlEscaping()
  .registerTypeAdapter(ActorGson.class, new ActorGsonSerializer())
  .create();
 
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
 
ActorGson rudyYoungblood = new ActorGson("nm2199632",
  sdf.parse("21-09-1982"), Arrays.asList("Apocalypto","Beatdown", "Wind Walkers"));
MovieWithNullValue movieWithNullValue = new MovieWithNullValue(null,
  "Mel Gibson", Arrays.asList(rudyYoungblood));
 
String serializedMovie = gson.toJson(movieWithNullValue);

结果如下:

{
  "imdbId": null,
  "actors": [
    {
      "<strong>IMDB Code</strong>": "nm2199632",
      "<strong>Date Of Birth</strong>": "21-09-1982",
      "<strong>N° Film:</strong> ": 3,
      "filmography": "Apocalypto-Beatdown-Wind Walkers"
    }
  ]
}

请注意:

  • 输出被格式化
  • 某些属性名称已更改并包含 HTML
  • 包含空值,并且省略了director字段
  • 日期现在采用dd-MM-yyyy格式
  • 出现了一个新的属性*——N° Film*
  • filmography 是一个格式化的属性,而不是默认的 JSON 列表

4. Gson反序列化

4.1. 简单反序列化

反序列化将 JSON 输入转换为 Java 对象。为了说明输出,我们在两个实体类中实现了*toString()*方法:

public class Movie {
    @Override
    public String toString() {
      return "Movie [imdbId=" + imdbId + ", director=" + director + ",actors=" + actors + "]";
    }
    ...
}
public class ActorGson {
    @Override
    public String toString() {
        return "ActorGson [imdbId=" + imdbId + ", dateOfBirth=" + dateOfBirth +
          ",filmography=" + filmography + "]";
    }
    ...
}

然后我们利用序列化的 JSON 并通过标准的 Gson 反序列化运行它:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"actors\":" +
  "[{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\"," +
  "\"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}";
        
Movie outputMovie = new Gson().fromJson(jsonInput, Movie.class);
outputMovie.toString();

输出是我们的实体,填充了来自 JSON 输入的数据:

Movie [imdbId=tt0472043, director=null, actors=[ActorGson 
  [imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982, 
  filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

与简单序列化程序的情况一样:

  • JSON 输入名称必须与 Java 实体名称相对应,否则它们被设置为 null。
  • dateOfBirth字段使用默认的 Gson 日期模式进行转换,忽略时区。

4.2. 自定义反序列化

使用自定义解串器允许我们修改标准解串器行为。在这种情况下,我们希望日期反映dateOfBirth的正确时区。我们在ActorGson实体上使用自定义的ActorGsonDeserializer来实现这一点:

public class ActorGsonDeserializer implements JsonDeserializer<ActorGson> {
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    @Override
    public ActorGson deserialize(JsonElement json, Type type,
      JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        
        JsonObject jsonObject = json.getAsJsonObject();
        JsonElement jsonImdbId = jsonObject.get("imdbId");
        JsonElement jsonDateOfBirth = jsonObject.get("dateOfBirth");
        JsonArray jsonFilmography = jsonObject.getAsJsonArray("filmography");
        ArrayList<String> filmList = new ArrayList<String>();
        if (jsonFilmography != null) {
            for (int i = 0; i < jsonFilmography.size(); i++) {
                filmList.add(jsonFilmography.get(i).getAsString());
            }
        }
    ActorGson actorGson = new ActorGson(jsonImdbId.getAsString(),
      sdf.parse(jsonDateOfBirth.getAsString()), filmList);
        return actorGson;
    }
}

我们使用SimpleDateFormat解析器来解析输入日期,并考虑时区。 请注意,我们本可以决定只为 Date 编写一个自定义反序列化器,但ActorGsonDeserializer提供了反序列化过程的更详细视图。 另请注意,Gson 方法不需要修改ActorGson实体,这是理想的,因为我们可能并不总是可以访问输入实体。我们在这里使用自定义反序列化器:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"actors\":"
  + "[{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\",
  + \"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}";
Gson gson = new GsonBuilder()
  .registerTypeAdapter(ActorGson.class,new ActorGsonDeserializer())
  .create();
Movie outputMovie = gson.fromJson(jsonInput, Movie.class);
outputMovie.toString();

输出类似于简单的反序列化结果,除了日期使用正确的时区:

Movie [imdbId=tt0472043, director=null, actors=[ActorGson
  [imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982, 
  filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

5. Jackson Maven 依赖

<dependency> 
    <groupId>com.fasterxml.jackson.core</groupId> 
    <artifactId>jackson-databind</artifactId>   
    <version>${jackson.version}</version> 
</dependency>

您可以在此处 获取最新版本的 Jackson 。

6. Jackson

6.1. 简单序列化

在这里,我们将使用 Jackson 使用以下实体获取与 Gson 相同的序列化内容。请注意,实体的 getter/setter 必须是公共的:

public class ActorJackson {
    private String imdbId;
    private Date dateOfBirth;
    private List<String> filmography;
    
    // required getters and setters, default constructor 
    // and field constructor details omitted
}
public class Movie {
    private String imdbId;
    private String director;
    private List<ActorJackson> actors;
    
    // required getters and setters, default constructor 
    // and field constructor details omitted
}
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); 
ActorJackson rudyYoungblood = new ActorJackson("nm2199632",sdf.parse("21-09-1982"),
  Arrays.asList("Apocalypto","Beatdown","Wind Walkers") ); 
Movie movie = new Movie("tt0472043","Mel Gibson", Arrays.asList(rudyYoungblood)); 
ObjectMapper mapper = new ObjectMapper(); 
String jsonResult = mapper.writeValueAsString(movie);

输出如下:

{"imdbId":"tt0472043","director":"Mel Gibson","actors":
[{"imdbId":"nm2199632","dateOfBirth":401439600000,
"filmography":["Apocalypto","Beatdown","Wind Walkers"]}]}

一些有趣的笔记:

  • ObjectMapper是我们的 Jackson 序列化器/反序列化器
  • 输出 JSON 未格式化
  • 默认情况下,Java Date 被转换为long

6.2. 自定义序列化

我们可以通过为我们的实体扩展 StdSerializer 来为ActorJackson元素生成创建一个 Jackson 序列化器。再次注意实体 getter/setter 必须是公共的:

public class ActorJacksonSerializer extends StdSerializer<ActorJackson> {
    private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
    public ActorJacksonSerializer(Class t) {
        super(t);
    }
    @Override
    public void serialize(ActorJackson actor, JsonGenerator jsonGenerator,
      SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("imdbId", actor.getImdbId());
        jsonGenerator.writeObjectField("dateOfBirth",
          actor.getDateOfBirth() != null ?
          sdf.format(actor.getDateOfBirth()) : null);
    
        jsonGenerator.writeNumberField("N° Film: ", 
          actor.getFilmography() != null ? actor.getFilmography().size() : null);
    jsonGenerator.writeStringField("filmography", actor.getFilmography()
          .stream().collect(Collectors.joining("-")));
        jsonGenerator.writeEndObject();
    }
}

我们创建一个 Movie 实体以允许忽略director字段:

public class MovieWithNullValue {
    
    private String imdbId;
    
    @JsonIgnore
    private String director;
    
    private List<ActorJackson> actors;
    
    // required getters and setters, default constructor
    // and field constructor details omitted
}

现在我们可以继续创建和设置自定义ObjectMapper

SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
ActorJackson rudyYoungblood = new ActorJackson(
  "nm2199632", 
  sdf.parse("21-09-1982"), 
  Arrays.asList("Apocalypto", "Beatdown","Wind Walkers"));
MovieWithNullValue movieWithNullValue = 
  new MovieWithNullValue(null,"Mel Gibson", Arrays.asList(rudyYoungblood));
SimpleModule module = new SimpleModule();
module.addSerializer(new ActorJacksonSerializer(ActorJackson.class));
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.registerModule(module)
  .writer(new DefaultPrettyPrinter())
  .writeValueAsString(movieWithNullValue);

输出是格式化的 JSON,它处理值、格式化日期、排除director字段并显示的新输出:

{
  "actors" : [ {
    "imdbId" : "nm2199632",
    "dateOfBirth" : "21-09-1982",
    "N° Film: " : 3,
    "filmography" : "Apocalypto-Beatdown-Wind Walkers"
  } ],
  "imdbID" : null
}

7. Jackson反序列化

7.1. 简单反序列化

为了说明输出,我们在两个 Jackson 实体类中实现了*toString()*方法:

public class Movie {
    @Override
    public String toString() {
        return "Movie [imdbId=" + imdbId + ", director=" + director
          + ", actors=" + actors + "]";
    }
    ...
}
public class ActorJackson {
    @Override
    public String toString() {
        return "ActorJackson [imdbId=" + imdbId + ", dateOfBirth=" + dateOfBirth
          + ", filmography=" + filmography + "]";
    }
    ...
}

然后我们利用序列化的 JSON 并通过 Jackson 反序列化运行它:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"actors\":
  [{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\",
  \"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}";
ObjectMapper mapper = new ObjectMapper();
Movie movie = mapper.readValue(jsonInput, Movie.class);

输出是我们的实体,填充了来自 JSON 输入的数据:

Movie [imdbId=tt0472043, director=null, actors=[ActorJackson 
  [imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982, 
  filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

与简单序列化程序的情况一样:

  • JSON 输入名称必须与 Java 实体名称相对应,或者设置为null
  • dateOfBirth字段使用默认的Jackson日期模式进行翻译,忽略时区。

7.2. 自定义反序列化

使用自定义解串器允许我们修改标准解串器行为。 在这种情况下,我们希望日期反映dateOfBirth 的正确时区,因此我们将 DateFormatter 添加到我们的 Jackson ObjectMapper中:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"director\":\"Mel Gibson\",
  \"actors\":[{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\",
  \"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}";
ObjectMapper mapper = new ObjectMapper();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
mapper.setDateFormat(df);
        
Movie movie = mapper.readValue(jsonInput, Movie.class);
movie.toString();

输出反映带有日期的正确时区:

Movie [imdbId=tt0472043, director=Mel Gibson, actors=[ActorJackson 
  [imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982, 
  filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

这个解决方案干净简单。

或者,我们可以为ActorJackson类创建一个自定义反序列化器,使用我们的ObjectMapper注册这个模块,并使用ActorJackson实体上的*@JsonDeserialize*注解反序列化日期。

这种方法的缺点是需要修改实体,这对于我们无法访问输入实体类的情况可能并不理想。