Gson – 反序列化不同类型

huangapple go评论78阅读模式
英文:

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(&quot;data&quot;)
	@Expose(serialize = false, deserialize = true)
	private final DataDto data;

	@SuppressWarnings(&quot;unused&quot;)
	private WrapperDto(@Nullable final DataDto data) {
		this.data = data;
	}

	@Nullable
	&lt;E extends Event&gt; E toEvent() {
		if ( data == null ) {
			return null;
		}
		return data.toEvent();
	}

	private static final class DataDto {

		@Nullable
		@SerializedName(&quot;tradeEvent&quot;)
		@Expose(serialize = false, deserialize = true)
		private final Event.Trade tradeEvent;

		@Nullable
		@SerializedName(&quot;errorEvent&quot;)
		@Expose(serialize = false, deserialize = true)
		private final Event.Error errorEvent;

		@SuppressWarnings(&quot;unused&quot;)
		private DataDto(@Nullable final Event.Trade tradeEvent, @Nullable final Event.Error errorEvent) {
			this.tradeEvent = tradeEvent;
			this.errorEvent = errorEvent;
		}

		@Nullable
		private &lt;E extends Event&gt; 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(&quot;Ambiguity detected. event=&quot; + event.getClass().getSimpleName() + &quot;, bestEvent=&quot; + bestEvent.getClass().getSimpleName());
				}
			}
			@SuppressWarnings(&quot;unchecked&quot;)
			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(&quot;tradeEvent.json&quot;) ) {
	Assertions.assertTrue(gson.&lt;WrapperDto&gt;fromJson(jsonReader, WrapperDto.class).toEvent() instanceof Event.Trade);
}
try ( final JsonReader jsonReader = open(&quot;errorEvent.json&quot;) ) {
	Assertions.assertTrue(gson.&lt;WrapperDto&gt;fromJson(jsonReader, WrapperDto.class).toEvent() instanceof Event.Error);
}
Assertions.assertThrows(IllegalStateException.class, () -&gt; {
	try ( final JsonReader jsonReader = open(&quot;tradeAndErrorEvent.json&quot;) ) {
		gson.&lt;WrapperDto&gt;fromJson(jsonReader, WrapperDto.class).toEvent();
	}
});

huangapple
  • 本文由 发表于 2020年8月20日 17:15:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/63501950.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定