Contents

Jackson的整数序列化布尔值

1. 简介

在处理 JSON 时, Jackson 库 是 Java 世界中的事实标准。尽管 Jackson 有明确定义的默认值,但为了将Boolean值映射到Integer,我们仍然需要进行手动配置。 当然,一些开发人员想知道如何以最好的方式并以最少的努力实现这一目标。

在本文中,我们将解释如何将Boolean值序列化为Integer——在 Jackson 中反之亦然。

2. 序列化

首先,我们将研究序列化部分。为了测试BooleanInteger的序列化,让我们定义我们的模型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.1. 现场级配置

序列化为Integer的一种非常简单的方法是使用*@JsonFormat注解我们的Boolean字段并为其设置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 开箱即用地支持IntegerBoolean反序列化。

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 识别并使用它。

如果我们只需要对有限数量的字段进行此行为,我们可以选择带有@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 Modules将序列化器添加到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值。

让我们通过扩展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);