概述
在Java世界中,当处理JSON时,Jackson库已经成为事实上的标准。尽管Jackson有明确的默认设置,但在将Boolean值映射为Integer时,我们仍需要进行手动配置。
当然,有些开发者会想知道如何以最有效和最少努力的方式实现这一点。本文将解释如何使用Jackson将Boolean值转换为Integer,以及反过来的序列化和反序列化过程。
1. 引言
首先定义我们的模型——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. 序列化
2.1. 字段级别配置
一个直接的序列化为Integer的方法是为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输出中所见,Boolean字段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);
结果,Jackson原生支持将Integer反序列化为Boolean。
4. 数字字符串而非整数
另一种情况是使用数字字符串(如"1"
和"0"
)而不是整数。在这种情况下,将Boolean值序列化为数字字符串或将它们反序列化回Boolean需要更多工作。
4.1. 序列化为数字字符串
要将Boolean值序列化为数字字符串等效值,我们需要定义自定义序列化器。
首先,创建我们的NumericBooleanSerializer
,继承自Jackson的JsonSerializer
:
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能够识别并使用它。
如果只需要为有限数量的字段应用此行为,我们可以通过@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\"}");
结果,只要使用相同的ObjectMapper
实例,Jackson将所有Boolean
类型的字段序列化为数字字符串。
4.2. 从数字字符串反序列化
类似于序列化,这次我们将定义一个自定义反序列化器来将数字字符串解析为Boolean值。
创建我们的类NumericBooleanDeserializer
,继承自JsonDeserializer
:
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值序列化为整数和数字字符串,以及如何反序列化它们。源代码示例和其他更多内容可在GitHub上找到。