概述

在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字段,输出将是默认值——truefalse

{"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字段pausedover已转换为数字10。我们可以看到,值是以整数形式表示的,因为它们没有被引号包围。

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可以默认解析仅包含10的数字为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字段pausedover添加注解:

@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上找到。