英文:
Gson - deserialising different types
问题
我正在处理传入的 JSON 字符串,并希望使用 GSON 将它们反序列化为具有类型的 POJO 对象。然而,发送字符串的服务器可以发送不同类型的对象 - 尽管类型在 JSON 负载中定义。
因此,看一下下面的两个 JSON 字符串,其中我有一个 tradeEvent 和一个 errorEvent 对象(还有 5 种其他类型,比如 settlementEvent、paymentEvent 等)。
在 GSON 中,我如何将其反序列化为实际的 POJO(可能使用泛型),因为在运行时我不知道类型 - 如您所见,第二级元素包含实际对象类型(tradeEvent、errorEvent 等)。
还应补充一点 - 就 POJO 而言,我应该将第二个元素(即 tradeEvent、errorEvent)表示为对象还是字符串?
{
"data": {
"tradeEvent": {
"tradeId": "2d28d464-a746-4c58-b19f-b586d2f5d015",
"status": 2,
"eventDescription": "Trade Settled"
}
}
}
{
"data": {
"errorEvent": {
"Uuid": "3a36ae26-ba41-40d5-b11d-d8d842eb2356",
"failureCode": 2,
"tradeId": "2d28d464-a746-4c58-b19f-b586d2f5d015",
"errorMessage": "Returned error: Exception while processing transaction: trade not matched"
}
}
}
感谢任何指导。
英文:
I am processing incoming JSON Strings and want to deserialise them into typed POJO objects using GSON.
However the server pushing the strings can send different types of object - though the type is defined in the JSON payload.
So looking at the below two JSON strings where I have a tradeEvent and an errorEvent object (have 5 other types like settlementEvent, paymentEvent etc).
How could I deserialise this to the actual POJO in GSON (presumably using generics) since I don't know the type until runtime - as you can see the second level element contains the actual object type (tradeEvent, errorEvent etc).
Should also add - in terms of the POJO, would I represent the second element (ie tradeEvent, errorEvent) as an object, or String?
{
"data": {
"tradeEvent": {
"tradeId": "2d28d464-a746-4c58-b19f-b586d2f5d015",
"status": 2,
"eventDescription": "Trade Settled"
}
}
}
{
"data": {
"errorEvent": {
"Uuid": "3a36ae26-ba41-40d5-b11d-d8d842eb2356",
"failureCode": 2, "tradeId": "2d28d464-a746-4c58-b19f-b586d2f5d015", "errorMessage": "Returned error: Exception while processing transaction: trade not matched"
}
}
}
Thanks for any guidance.
答案1
得分: 1
实现一个数据包装类以提取事件对象可能是最简单的方法:
final class WrapperDto {
@Nullable
@SerializedName("data")
@Expose(serialize = false, deserialize = true)
private final DataDto data;
@SuppressWarnings("unused")
private WrapperDto(@Nullable final DataDto data) {
this.data = data;
}
@Nullable
<E extends Event> E toEvent() {
if (data == null) {
return null;
}
return data.toEvent();
}
private static final class DataDto {
@Nullable
@SerializedName("tradeEvent")
@Expose(serialize = false, deserialize = true)
private final Event.Trade tradeEvent;
@Nullable
@SerializedName("errorEvent")
@Expose(serialize = false, deserialize = true)
private final Event.Error errorEvent;
@SuppressWarnings("unused")
private DataDto(@Nullable final Event.Trade tradeEvent, @Nullable final Event.Error errorEvent) {
this.tradeEvent = tradeEvent;
this.errorEvent = errorEvent;
}
@Nullable
private <E extends Event> E toEvent() throws IllegalStateException {
@Nullable
Event bestEvent = null;
for (final Event event : new Event[]{tradeEvent, errorEvent}) {
if (bestEvent == null) {
bestEvent = event;
} else if (event != null) {
throw new IllegalStateException("Ambiguity detected. event=" + event.getClass().getSimpleName() + ", bestEvent=" + bestEvent.getClass().getSimpleName());
}
}
@SuppressWarnings("unchecked")
final E castBestEvent = (E) bestEvent;
return castBestEvent;
}
}
}
我认为这种方法比适应 RuntimeTypeAdapterFactory 到您的需求中要容易得多。然而,实现自定义类型适配器可能会在读取时检测到模糊字段,因此不会对每个字段进行反序列化(这会消耗一些额外的堆内存)。
上述方法将通过以下测试:
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.excludeFieldsWithoutExposeAnnotation()
.create();
// ... (测试代码部分略)
英文:
Implementing a data wrapper class to extract an event object would probably be the most simple way:
final class WrapperDto {
@Nullable
@SerializedName("data")
@Expose(serialize = false, deserialize = true)
private final DataDto data;
@SuppressWarnings("unused")
private WrapperDto(@Nullable final DataDto data) {
this.data = data;
}
@Nullable
<E extends Event> E toEvent() {
if ( data == null ) {
return null;
}
return data.toEvent();
}
private static final class DataDto {
@Nullable
@SerializedName("tradeEvent")
@Expose(serialize = false, deserialize = true)
private final Event.Trade tradeEvent;
@Nullable
@SerializedName("errorEvent")
@Expose(serialize = false, deserialize = true)
private final Event.Error errorEvent;
@SuppressWarnings("unused")
private DataDto(@Nullable final Event.Trade tradeEvent, @Nullable final Event.Error errorEvent) {
this.tradeEvent = tradeEvent;
this.errorEvent = errorEvent;
}
@Nullable
private <E extends Event> E toEvent()
throws IllegalStateException {
@Nullable
Event bestEvent = null;
for ( final Event event : new Event[]{ tradeEvent, errorEvent } ) {
if ( bestEvent == null ) {
bestEvent = event;
} else if ( event != null ) {
throw new IllegalStateException("Ambiguity detected. event=" + event.getClass().getSimpleName() + ", bestEvent=" + bestEvent.getClass().getSimpleName());
}
}
@SuppressWarnings("unchecked")
final E castBestEvent = (E) bestEvent;
return castBestEvent;
}
}
}
This approach, I believe, is much easier to implement than adapting RuntimeTypeAdapterFactory to your needs. However, implementing a custom type adapter might detect ambiguous fields right on reading therefore not deserializing each field (that costs some more heap).
The approach above would pass the following test:
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.excludeFieldsWithoutExposeAnnotation()
.create();
...
try ( final JsonReader jsonReader = open("tradeEvent.json") ) {
Assertions.assertTrue(gson.<WrapperDto>fromJson(jsonReader, WrapperDto.class).toEvent() instanceof Event.Trade);
}
try ( final JsonReader jsonReader = open("errorEvent.json") ) {
Assertions.assertTrue(gson.<WrapperDto>fromJson(jsonReader, WrapperDto.class).toEvent() instanceof Event.Error);
}
Assertions.assertThrows(IllegalStateException.class, () -> {
try ( final JsonReader jsonReader = open("tradeAndErrorEvent.json") ) {
gson.<WrapperDto>fromJson(jsonReader, WrapperDto.class).toEvent();
}
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论