1. 簡介
在本文中,我們將比較 Gson 和 Jackson API 用於將 JSON 數據序列化為 Java 對象和反序列化為 Java 對象。
Gson 和 Jackson 都是完整的庫,提供 JSON 數據綁定支持用於 Java。它們都是積極開發、開源的項目,可以處理複雜的類型並支持 Java 通用類型。
在大多數情況下,這兩個庫都可以將實體反序列化到實體中,而無需修改實體類,這在開發人員無法訪問實體源代碼的情況下非常重要。
2. Gson Maven 依賴
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
您可以在這裏獲取 Gson 的最新版本 這裏。
3. Gson Serialization
Serialization converts Java objects to JSON output. Consider the following entities:
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. Simple Serialization
Let’s start with an example of Java to JSON serialization:
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);
This will result in:
{
"imdbId": "tt0472043",
"director": "Mel Gibson",
"actors": [{
"imdbId": "nm2199632",
"dateOfBirth": "Sep 21, 1982 12:00:00 AM",
"filmography": ["Apocalypto", "Beatdown", "Wind Walkers"]
}]
}
By default:
- All properties are serialized because they have no null values
- dateOfBirth field was translated with the default Gson date pattern
- Output is not formatted and JSON property names correspond to the Java entities
3.2. Custom Serialization
Using a custom serializer allows us to modify the standard behavior. We can introduce an output formatter with HTML, handle null values, exclude properties from output, or add a new output.
ActorGsonSerializer modifies generation of JSON code for the ActorGson element:
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("IMDB Code", actor.getImdbId());
actorJsonObj.addProperty("Date Of Birth",
actor.getDateOfBirth() != null ?
sdf.format(actor.getDateOfBirth()) : null);
actorJsonObj.addProperty("N° Film: ",
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("-"));
}
}
In order to exclude the director property, the @Expose annotation is used for properties we want to consider:
public class MovieWithNullValue {
@Expose
private String imdbId;
private String director;
@Expose
private List<ActorGson> actors;
}
Now we can proceed with Gson object creation using the GsonBuilder class:
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);
The result is the following:
{
"imdbId": null,
"actors": [
{
"IMDB Code": "nm2199632",
"Date Of Birth": "21-09-1982",
"N° Film: ": 3,
"filmography": "Apocalypto-Beatdown-Wind Walkers"
}
]
}
Notice that:
- the output is formatted
- some property names are changed and contain HTML
- null values are included, and the director field is omitted
- Date is now in the dd-MM-yyyy format
- a new property is present – N° Film
- filmography is a formatted property, not the default JSON list
4. Gson Deserialization
4.1. Simple Deserialization
Deserialization converts JSON input into Java objects. To illustrate the output, we implement the toString() method in both entity classes:
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 + "]";
}
...
}
Then we utilize the serialized JSON and run it through standard Gson deserialization:
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();
The output is our entities, populated with the data from our JSON input:
Movie [imdbId=tt0472043, director=null, actors=[ActorGson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
As was the case with the simple serializer:
- the JSON input names must correspond with the Java entity names, or they are set to null.
- dateOfBirth field was translated with the default Gson date pattern, ignoring the time zone.
4.2. Custom Deserialization
Using a custom deserializer allows us to modify the standard deserializer behavior. In this case, we want the date to reflect the correct time zone for dateOfBirth. We use a custom ActorGsonDeserializer on the ActorGson entity to achieve this:
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;
}
}
We employed a SimpleDateFormat parser to parse the input date, accounting for the time zone.
Note that we could have decided to simply write a custom deserializer for only the Date, but the ActorGsonDeserializer offers a more detailed view of the deserialization process.
Also note that the Gson approach does not require modifying the ActorGson entity, which is ideal as we may not always have access to the input entity. We use the custom deserializer here:
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();
The output is similar to the simple deserializer result, except the date uses correct time zone:
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>2.17.2</version>
</dependency>
您可以在這裏獲取 Jackson 的最新版本:這裏。
6. Jackson Serialization
6.1. Simple Serialization
Here we will use Jackson to obtain the same serialized content we had with Gson using the following entities. Note that the entity’s getters/setters must be public:
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);
The output is as follows:
{"imdbId":"tt0472043","director":"Mel Gibson","actors":
[{"imdbId":"nm2199632","dateOfBirth":401439600000,
"filmography":["Apocalypto","Beatdown","Wind Walkers"]}]}
Some notes of interest:
ObjectMapperis our Jackson serializer/deserializer- The output JSON is not formatted
- By default, Java Date is translated to
longvalue
6.2. Custom Serialization
We can create a Jackson serializer for ActorJackson element generation by extending StdSerializer for our entity. Again note that the entity getters/setters must be public:
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();
}
}
We create a Movie entity to allow ignoring of the director field:
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
}
Now we can proceed with a custom ObjectMapper creation and setup:
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);
The output is formatted JSON that handles null values, formats the date, excludes the director field and shows new output of N°:
{
"actors" : [ {
"imdbId" : "nm2199632",
"dateOfBirth" : "21-09-1982",
"N° Film: " : 3,
"filmography" : "Apocalypto-Beatdown-Wind Walkers"
} ],
"imdbID" : null
}
method in both Jackson entity classes:
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 + "]";
}
...
}
Then we utilize the serialized JSON and run it through Jackson deserialization:
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);
The output is our entities, populated with the data from our JSON input:
Movie [imdbId=tt0472043, director=null, actors=[ActorJackson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
As was the case with the simple serializer:
, so we add a DateFormatter to our Jackson :
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();
The output reflects the correct time zone with the date:
Movie [imdbId=tt0472043, director=Mel Gibson, actors=[ActorJackson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
This solution is clean and simple.
Alternatively, we could have created a custom deserializer for the class, registered this module with our , and deserialized the date using the annotation on the entity.
The disadvantage of that approach is the need to modify the entity, which may not be ideal for cases when we don’t have access to the input entity classes.
8. 結論
Gson 和 Jackson 都是用於序列化/反序列化 JSON 數據的良好選擇,易於使用且文檔完善。
Gson 的優點:
- 簡單地使用 toJson/fromJson 在簡單情況下
- 在反序列化時,不需要訪問 Java 實體
Jackson 的優點:
- 內置於所有 JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) 和 Spring 框架
- 廣泛的註解支持
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 + "]";
}
...
}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);Movie [imdbId=tt0472043, director=null, actors=[ActorJackson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]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();
The output reflects the correct time zone with the date:
Movie [imdbId=tt0472043, director=Mel Gibson, actors=[ActorJackson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
This solution is clean and simple.
Alternatively, we could have created a custom deserializer for the class, registered this module with our , and deserialized the date using the annotation on the entity.
The disadvantage of that approach is the need to modify the entity, which may not be ideal for cases when we don’t have access to the input entity classes.
8. 結論
Gson 和 Jackson 都是用於序列化/反序列化 JSON 數據的良好選擇,易於使用且文檔完善。
Gson 的優點:
- 簡單地使用 toJson/fromJson 在簡單情況下
- 在反序列化時,不需要訪問 Java 實體
Jackson 的優點:
- 內置於所有 JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) 和 Spring 框架
- 廣泛的註解支持