gpt4 book ai didi

java - 在 Java 中测试隔离的自定义 JsonDeserializer

转载 作者:行者123 更新时间:2023-12-02 09:14:49 25 4
gpt4 key购买 nike

因此,对于我正在编写的这个小程序,我希望解析 Twitter 的推文流。我使用 Gson 库,效果很好。 Gson 无法解析 Twitter 的 created_at 日期时间字段,因此我必须编写一个自定义的 JsonDserializer,需要通过 GsonBuilder 向解析器注册如下:

new GsonBuilder().registerTypeAdatapter(DateTime.class, <myCustomDeserializerType>)

现在我的反序列化器运行良好,我能够解析 Twitter 的流。

但是,我试图通过单元测试覆盖尽可能多的程序,因此应该包含这个自定义反序列化器。

由于良好的单元测试是一个很好的隔离测试,因此我不想将其注册到 Gson 对象,然后再解析 json 字符串。我想要的是创建解串器的实例,并仅传递表示日期时间的通用字符串,以便我可以测试解串器,而无需将其与其他任何东西集成。

JsonDeserializer 的反序列化方法的签名如下:

deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)

假设我想解析以下数据:“Mon Mar 27 14:09:47 +0000 2017”。我必须如何转换输入数据才能正确测试我的解串器。

我不是在寻找实际解析此日期的代码,我已经涵盖了该部分。我问如何满足 deserialize 方法的签名,以便我可以模拟它在使用它的 Gson 中的使用。

最佳答案

JsonSerializerJsonDeserializer 与 Gson JSON 树模型和特定的 Gson 配置(反)序列化上下文紧密绑定(bind),该上下文提供了一组可以序列化(反序列化)的类型。因此,完成 JsonSerializerJsonDeserializer 的单元测试相当困难而不容易。

考虑 src/test/resources/.../zoned-date-time.json 中的以下 JSON 文档:

"Mon Mar 27 14:09:47 +0000 2017"

这是一个完全有效的 JSON 文档,为了简单起见,它除了一个字符串之外什么都没有。上述格式的日期/时间格式化程序可以在 Java 8 中实现,如下所示:

final class CustomPatterns {

private CustomPatterns() {
}

private static final Map<Long, String> dayOfWeek = ImmutableMap.<Long, String>builder()
.put(1L, "Mon")
.put(2L, "Tue")
.put(3L, "Wed")
.put(4L, "Thu")
.put(5L, "Fri")
.put(6L, "Sat")
.put(7L, "Sun")
.build();

private static final Map<Long, String> monthOfYear = ImmutableMap.<Long, String>builder()
.put(1L, "Jan")
.put(2L, "Feb")
.put(3L, "Mar")
.put(4L, "Apr")
.put(5L, "May")
.put(6L, "Jun")
.put(7L, "Jul")
.put(8L, "Aug")
.put(9L, "Sep")
.put(10L, "Oct")
.put(11L, "Nov")
.put(12L, "Dec")
.build();

static final DateTimeFormatter customDateTimeFormatter = new DateTimeFormatterBuilder()
.appendText(DAY_OF_WEEK, dayOfWeek)
.appendLiteral(' ')
.appendText(MONTH_OF_YEAR, monthOfYear)
.appendLiteral(' ')
.appendValue(DAY_OF_MONTH, 1, 2, NOT_NEGATIVE)
.appendLiteral(' ')
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.appendLiteral(' ')
.appendOffset("+HHMM", "+0000")
.appendLiteral(' ')
.appendValue(YEAR)
.toFormatter();

}

现在考虑以下用于 ZonedDateTime 的 JSON 反序列化器:

final class ZonedDateTimeJsonDeserializer
implements JsonDeserializer<ZonedDateTime> {

private static final JsonDeserializer<ZonedDateTime> zonedDateTimeJsonDeserializer = new ZonedDateTimeJsonDeserializer();

private ZonedDateTimeJsonDeserializer() {
}

static JsonDeserializer<ZonedDateTime> getZonedDateTimeJsonDeserializer() {
return zonedDateTimeJsonDeserializer;
}

@Override
public ZonedDateTime deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
try {
final String s = context.deserialize(jsonElement, String.class);
return ZonedDateTime.parse(s, customDateTimeFormatter);
} catch ( final DateTimeParseException ex ) {
throw new JsonParseException(ex);
}
}

}

请注意,我通过意图通过上下文反序列化字符串,以强调更复杂的JsonDeserializer实例可能严重依赖它。现在让我们进行一些 JUnit 测试来测试它:

public final class ZonedDateTimeJsonDeserializerTest {

private static final TypeToken<ZonedDateTime> zonedDateTimeTypeToken = new TypeToken<ZonedDateTime>() {
};

private static final ZonedDateTime expectedZonedDateTime = ZonedDateTime.of(2017, 3, 27, 14, 9, 47, 0, UTC);

@Test
public void testDeserializeIndirectlyViaAutomaticTypeAdapterBinding()
throws IOException {
final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer();
final Gson gson = new GsonBuilder()
.registerTypeAdapter(ZonedDateTime.class, unit)
.create();
try ( final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json") ) {
final ZonedDateTime actualZonedDateTime = gson.fromJson(jsonReader, ZonedDateTime.class);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
}

@Test
public void testDeserializeIndirectlyViaManualTypeAdapterBinding()
throws IOException {
final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer();
final Gson gson = new Gson();
final TypeAdapterFactory typeAdapterFactory = newFactoryWithMatchRawType(zonedDateTimeTypeToken, unit);
final TypeAdapter<ZonedDateTime> dateTypeAdapter = typeAdapterFactory.create(gson, zonedDateTimeTypeToken);
try ( final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json") ) {
final ZonedDateTime actualZonedDateTime = dateTypeAdapter.read(jsonReader);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
}

@Test
public void testDeserializeDirectlyWithMockedContext()
throws IOException {
final JsonDeserializer<ZonedDateTime> unit = getZonedDateTimeJsonDeserializer();
final JsonDeserializationContext mockContext = mock(JsonDeserializationContext.class);
when(mockContext.deserialize(any(JsonElement.class), eq(String.class))).thenAnswer(iom -> {
final JsonElement jsonElement = (JsonElement) iom.getArguments()[0];
return jsonElement.getAsJsonPrimitive().getAsString();
});
final JsonParser parser = new JsonParser();
try ( final JsonReader jsonReader = getPackageResourceJsonReader(ZonedDateTimeJsonDeserializerTest.class, "zoned-date-time.json") ) {
final JsonElement jsonElement = parser.parse(jsonReader);
final ZonedDateTime actualZonedDateTime = unit.deserialize(jsonElement, ZonedDateTime.class, mockContext);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
verify(mockContext).deserialize(any(JsonPrimitive.class), eq(String.class));
verifyNoMoreInteractions(mockContext);
}

}

请注意,这里的每个测试都需要构建一些 Gson 配置才能让反序列化上下文工作,否则必须模拟后者。几乎是为了测试一个简单的单元。

Gson 中 JSON 树模型的替代方案是面向流的类型适配器,它不需要构建整个 JSON 树,因此您可以轻松地直接从 JSON 流读取或写入,从而实现序列化(反)序列化更快、更少的内存消耗。特别是对于简单的情况,例如简单的 string<==>FooBar 转换是什么。

final class ZonedDateTimeTypeAdapter
extends TypeAdapter<ZonedDateTime> {

private static final TypeAdapter<ZonedDateTime> zonedDateTimeTypeAdapter = new ZonedDateTimeTypeAdapter().nullSafe();

private ZonedDateTimeTypeAdapter() {
}

static TypeAdapter<ZonedDateTime> getZonedDateTimeTypeAdapter() {
return zonedDateTimeTypeAdapter;
}

@Override
public void write(final JsonWriter out, final ZonedDateTime zonedDateTime) {
throw new UnsupportedOperationException();
}

@Override
public ZonedDateTime read(final JsonReader in)
throws IOException {
try {
final String s = in.nextString();
return ZonedDateTime.parse(s, customDateTimeFormatter);
} catch ( final DateTimeParseException ex ) {
throw new JsonParseException(ex);
}
}

}

这是上面类型适配器的简单单元测试:

public final class ZonedDateTimeTypeAdapterTest {

private static final ZonedDateTime expectedZonedDateTime = ZonedDateTime.of(2017, 3, 27, 14, 9, 47, 0, UTC);

@Test(expected = UnsupportedOperationException.class)
public void testWrite() {
final TypeAdapter<ZonedDateTime> unit = getZonedDateTimeTypeAdapter();
unit.toJsonTree(expectedZonedDateTime);
}

@Test
public void testRead()
throws IOException {
final TypeAdapter<ZonedDateTime> unit = getZonedDateTimeTypeAdapter();
try ( final Reader reader = getPackageResourceReader(ZonedDateTimeTypeAdapterTest.class, "zoned-date-time.json") ) {
final ZonedDateTime actualZonedDateTime = unit.fromJson(reader);
assertThat(actualZonedDateTime, is(expectedZonedDateTime));
}
}

}

对于简单的情况,我肯定会使用类型适配器,但它们可能有点难以实现。您还可以引用Gson unit tests了解更多信息。

关于java - 在 Java 中测试隔离的自定义 JsonDeserializer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43261395/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com