1. 引言
Jackson 庫是 Java 世界中處理 JSON 的事實標準。儘管 Jackson 具有明確定義的默認值,但對於將 Boolean 值映射到 Integer 時,我們仍然需要進行手動配置。
當然,一些開發者會想知道如何以最佳方式和最小的努力來實現這一點。
在本文中,我們將解釋如何將 Boolean 值序列化為 Integer 值(以及數字字符串)以及反之,在 Jackson 中。
2. 序列化
最初,我們將研究序列化部分。為了測試 Boolean 到 Integer 序列化的過程,我們先定義我們的模型,Game:
public class Game {
private Long id;
private String name;
private Boolean paused;
private Boolean over;
// constructors, getters and setters
}
如往常一樣,Game 對象的默認序列化將使用 Jackson 的 ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);
不出所料,Boolean 字段的輸出將是默認的——true 或 false:
{"id":1, "name":"My Game", "paused":true, "over":false}
但是,我們旨在從我們的 Game 對象中獲得以下 JSON 輸出作為最終結果:
{"id":1, "name":"My Game", "paused":1, "over":0}
2.1. 字段級別配置
一種相對簡單的方法是為我們的 Boolean 字段添加 @JsonFormat 註解,並將 Shape.NUMBER 設置為它:
@JsonFormat(shape = Shape.NUMBER)
private Boolean paused;
@JsonFormat(shape = Shape.NUMBER)
private Boolean over;
然後,我們嘗試在測試方法中進行序列化:
ObjectMapper mapper = new ObjectMapper();
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);
assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":1,\"over\":0}");
正如我們在 JSON 輸出中觀察到的,我們的 paused 和 over 字段已轉換為數字 1 和 0。我們可以看到,由於沒有用引號括起來,因此值以整數格式表示。
2.2. 全局配置
有時,為每個字段添加註解並不實用。例如,根據要求,我們可能需要全局配置我們的 Boolean 到 Integer 序列化。
幸運的是,Jackson 允許我們通過在 ObjectMapper 中覆蓋默認值來全局配置 @JsonFormat:
ObjectMapper mapper = new ObjectMapper();
mapper.configOverride(Boolean.class)
.setFormat(JsonFormat.Value.forShape(Shape.NUMBER));
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);
assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":1,\"over\":0}");
3. 反序列化
類似於地,我們也可以在反序列化 JSON 字符串為我們的模型時,從數字中獲取 Boolean 值。
幸運的是,Jackson 可以解析數字——僅 1 和 0 ——為 Boolean 值,默認情況下。因此,我們不需要使用 @JsonFormat 註解或任何其他配置。
因此,無需配置,讓我們通過另一個測試方法來查看這種行為:
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1,\"name\":\"My Game\",\"paused\":1,\"over\":0}";
Game game = mapper.readValue(json, Game.class);
assertThat(game.isPaused()).isEqualTo(true);
assertThat(game.isOver()).isEqualTo(false);
因此,Integer 到 Boolean 反序列化在 Jackson 中默認支持。
4. 數字字符串而不是整數
另一個用例是使用數字字符串——“1”和“0”——而不是整數。在這種情況下,將Boolean值序列化為數字字符串或反序列化為Boolean需要更多的努力。
4.1. 序列化為數字字符串
為了將Boolean值序列化為數字字符串等效值,我們需要定義一個自定義序列化器。
因此,讓我們通過擴展 Jackson 的 JsonSerializer 創建我們的 NumericBooleanSerializer:
public class NumericBooleanSerializer extends JsonSerializer<Boolean> {
@Override
public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value ? "1" : "0");
}
}
作為旁註,通常Boolean類型可以是null。但是,Jackson 內部處理這種情況,並且在value字段為null時,它不會考慮我們的自定義序列化器。因此,我們在這裏是安全的。
接下來,我們將自定義序列化器註冊到 Jackson 中,以便 Jackson 能夠識別並使用它。
如果我們需要這種行為僅限於有限數量的字段,我們可以選擇字段級別配置,使用 @JsonSerialize 註解。
相應地,我們標註我們的 Boolean 字段,paused 和 over:
@JsonSerialize(using = NumericBooleanSerializer.class)
private Boolean paused;
@JsonSerialize(using = NumericBooleanSerializer.class)
private Boolean over;
然後,我們嘗試在測試方法中進行序列化:
ObjectMapper mapper = new ObjectMapper();
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);
assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}");
雖然測試方法實現幾乎與之前的實現相同,但我們應該注意引號——“paused”:”1″, “over”:”0″——圍繞數字值的引號。這無疑表明這些值是實際的字符串,其中包含數字內容。
最後但並非最不重要,如果我們需要在任何地方執行此自定義序列化,Jackson 支持通過 Jackson 模塊向 ObjectMapper 添加序列化器以進行全局配置。
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Boolean.class, new NumericBooleanSerializer());
mapper.registerModule(module);
Game game = new Game(1L, "My Game");
game.setPaused(true);
game.setOver(false);
String json = mapper.writeValueAsString(game);
assertThat(json)
.isEqualTo("{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}");
結果是,Jackson 將所有 Boolean 類型的字段序列化為數字字符串,只要我們使用相同的 ObjectMapper 實例。
4.2. 從數字字符串反序列化
類似於序列化,現在我們將定義一個自定義反序列器來將數字字符串解析為 Boolean 值。
讓我們通過擴展 JsonDeserializer 創建我們的類 NumericBooleanDeserializer:
public class NumericBooleanDeserializer extends JsonDeserializer<Boolean> {
@Override
public Boolean deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
if ("1".equals(p.getText())) {
return Boolean.TRUE;
}
if ("0".equals(p.getText())) {
return Boolean.FALSE;
}
return null;
}
}
接下來,我們再次標註我們的 Boolean 字段,但這次使用 @JsonDeserialize:
@JsonSerialize(using = NumericBooleanSerializer.class)
@JsonDeserialize(using = NumericBooleanDeserializer.class)
private Boolean paused;
@JsonSerialize(using = NumericBooleanSerializer.class)
@JsonDeserialize(using = NumericBooleanDeserializer.class)
private Boolean over;
因此,我們寫另一個測試方法來查看我們的 NumericBooleanDeserializer 在行動中的情況:
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}";
Game game = mapper.readValue(json, Game.class);
assertThat(game.isPaused()).isEqualTo(true);
assertThat(game.isOver()).isEqualTo(false);
或者,我們也可以通過 Jackson 模塊全局配置我們的自定義反序列器:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Boolean.class, new NumericBooleanDeserializer());
mapper.registerModule(module);
String json = "{\"id\":1,\"name\":\"My Game\",\"paused\":\"1\",\"over\":\"0\"}";
Game game = mapper.readValue(json, Game.class);
assertThat(game.isPaused()).isEqualTo(true);
assertThat(game.isOver()).isEqualTo(false);
5. 結論
在本文中,我們描述瞭如何將 Boolean 值序列化為整數和數字字符串,以及如何將它們反序列化回原樣。