有时 API 响应不适合像 Java 这样的静态类型语言非常好。我想说的是,如果您在与不太方便的响应格式对齐时遇到问题,如果您希望它对您方便,您必须编写更多代码。在大多数情况下,Gson 可以提供帮助,但不是免费的。
有没有什么方法可以为这个 POJO 建模,让 Gson 识别这个结构,而不必手动处理这个场景?
没有。 Gson 不会混合不同结构的物体,所以你仍然需要告诉它你的意图。
有没有更好的方法来做到这一点?
我猜是的,既可以对响应进行建模,也可以实现这些响应的解析方式。
我是否遗漏了反序列化器可能失败或无法按预期工作的任何情况?
它像所有反序列化器一样对响应格式敏感,所以总的来说它已经足够好,但可以改进。
首先,让我们考虑一下您只能有两种情况:常规响应和错误。这是一个经典案例,可以这样建模:
abstract class ApiResponse<T> {
// A bunch of protected methods, no interface needed as we're considering it's a value type and we don't want to expose any of them
protected abstract boolean isSuccessful();
protected abstract T getData()
throws UnsupportedOperationException;
protected abstract List<ApiResponseError> getErrors()
throws UnsupportedOperationException;
// Since we can cover all two cases ourselves, let them all be here in this class
private ApiResponse() {
}
static <T> ApiResponse<T> success(final T data) {
return new SucceededApiResponse<>(data);
}
static <T> ApiResponse<T> failure(final List<ApiResponseError> errors) {
@SuppressWarnings("unchecked")
final ApiResponse<T> castApiResponse = (ApiResponse<T>) new FailedApiResponse(errors);
return castApiResponse;
}
// Despite those three protected methods can be technically public, let's encapsulate the state
final void accept(final IApiResponseConsumer<? super T> consumer) {
if ( isSuccessful() ) {
consumer.acceptSuccess(getData());
} else {
consumer.acceptFailure(getErrors());
}
}
// And make a couple of return-friendly accept methods
final T acceptOrNull() {
if ( !isSuccessful() ) {
return null;
}
return getData();
}
final T acceptOrNull(final Consumer<? super List<ApiResponseError>> errorsConsumer) {
if ( !isSuccessful() ) {
errorsConsumer.accept(getErrors());
return null;
}
return getData();
}
private static final class SucceededApiResponse<T>
extends ApiResponse<T> {
private final T data;
private SucceededApiResponse(final T data) {
this.data = data;
}
@Override
protected boolean isSuccessful() {
return true;
}
@Override
protected T getData() {
return data;
}
@Override
protected List<ApiResponseError> getErrors()
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
private static final class FailedApiResponse
extends ApiResponse<Void> {
private final List<ApiResponseError> errors;
private FailedApiResponse(final List<ApiResponseError> errors) {
this.errors = errors;
}
@Override
protected boolean isSuccessful() {
return false;
}
@Override
protected List<ApiResponseError> getErrors() {
return errors;
}
@Override
protected Void getData()
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
}
interface IApiResponseConsumer<T> {
void acceptSuccess(T data);
void acceptFailure(List<ApiResponseError> errors);
}
错误的简单映射:
final class ApiResponseError {
// Since incoming DTO are read-only data bags in most-most cases, even getters may be noise here
// Gson can strip off the final modifier easily
// However, primitive values are inlined by javac, so we're cheating javac with Integer.valueOf
final int code = Integer.valueOf(0);
final String message = null;
}
还有一些价值观:
final class Person {
final String name = null;
final int age = Integer.valueOf(0);
}
第二个组件是一个特殊类型的适配器,它告诉 Gson如何 API 响应必须被反序列化。请注意,类型适配器与JsonSerializer 和JsonDeserializer 不同,它以流方式工作,不需要将整个 JSON 模型 (JsonElement) 存储在内存中,因此您可以节省内存并提高大型 JSON 文档的性能。
final class ApiResponseTypeAdapterFactory
implements TypeAdapterFactory {
// No state, so it can be instantiated once
private static final TypeAdapterFactory apiResponseTypeAdapterFactory = new ApiResponseTypeAdapterFactory();
// Type tokens are effective value types and can be instantiated once per parameterization
private static final TypeToken<List<ApiResponseError>> apiResponseErrorsType = new TypeToken<List<ApiResponseError>>() {
};
private ApiResponseTypeAdapterFactory() {
}
static TypeAdapterFactory getApiResponseTypeAdapterFactory() {
return apiResponseTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Is it ApiResponse, a class we can handle?
if ( ApiResponse.class.isAssignableFrom(typeToken.getRawType()) ) {
// Trying to resolve its parameterization
final Type typeParameter = getTypeParameter0(typeToken.getType());
// And asking Gson for the success and failure type adapters to use downstream parsers
final TypeAdapter<?> successTypeAdapter = gson.getDelegateAdapter(this, TypeToken.get(typeParameter));
final TypeAdapter<List<ApiResponseError>> failureTypeAdapter = gson.getDelegateAdapter(this, apiResponseErrorsType);
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) new ApiResponseTypeAdapter<>(successTypeAdapter, failureTypeAdapter);
return castTypeAdapter;
}
return null;
}
private static Type getTypeParameter0(final Type type) {
// Is this type parameterized?
if ( !(type instanceof ParameterizedType) ) {
// No, it's raw
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
private static final class ApiResponseTypeAdapter<T>
extends TypeAdapter<ApiResponse<T>> {
private final TypeAdapter<T> successTypeAdapter;
private final TypeAdapter<List<ApiResponseError>> failureTypeAdapter;
private ApiResponseTypeAdapter(final TypeAdapter<T> successTypeAdapter, final TypeAdapter<List<ApiResponseError>> failureTypeAdapter) {
this.successTypeAdapter = successTypeAdapter;
this.failureTypeAdapter = failureTypeAdapter;
}
@Override
public void write(final JsonWriter out, final ApiResponse<T> value)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public ApiResponse<T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
// Is it array? Assuming that the responses come as arrays only
// Otherwise a more complex parsing is required probably replaced with JsonDeserializer for some cases
// So reading the next value (entire array) and wrapping it up in an API response with the success-on state
return success(successTypeAdapter.read(in));
case BEGIN_OBJECT:
// Otherwise it's probably an error object?
in.beginObject();
final String name = in.nextName();
if ( !name.equals("errors") ) {
// Let it fail fast, what if a successful response would be here?
throw new MalformedJsonException("Expected errors` but was " + name);
}
// Constructing a failed response object and terminating the error object
final ApiResponse<T> failure = failure(failureTypeAdapter.read(in));
in.endObject();
return failure;
// A matter of style, but just to show the intention explicitly and make IntelliJ IDEA "switch on enums with missing case" to not report warnings here
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
throw new AssertionError(token);
}
}
}
}
现在,如何将所有内容组合在一起。请注意,响应不会显式公开其内部结构,而是要求消费者接受将其私有内容真正封装起来。
public final class Q43113283 {
private Q43113283() {
}
private static final String SUCCESS_JSON = "[{\"name\":\"John\",\"age\":21},{\"name\":\"Sarah\",\"age\":32}]";
private static final String FAILURE_JSON = "{\"errors\":[{\"code\":1001,\"message\":\"Something blew up\"}]}";
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getApiResponseTypeAdapterFactory())
.create();
// Assuming that the Type instance is immutable under the hood so it might be cached
private static final Type personsApiResponseType = new TypeToken<ApiResponse<List<Person>>>() {
}.getType();
@SuppressWarnings("unchecked")
public static void main(final String... args) {
final ApiResponse<Iterable<Person>> successfulResponse = gson.fromJson(SUCCESS_JSON, personsApiResponseType);
final ApiResponse<Iterable<Person>> failedResponse = gson.fromJson(FAILURE_JSON, personsApiResponseType);
useFullyCallbackApproach(successfulResponse, failedResponse);
useSemiCallbackApproach(successfulResponse, failedResponse);
useNoCallbackApproach(successfulResponse, failedResponse);
}
private static void useFullyCallbackApproach(final ApiResponse<Iterable<Person>>... responses) {
System.out.println("<FULL CALLBACKS>");
final IApiResponseConsumer<Iterable<Person>> handler = new IApiResponseConsumer<Iterable<Person>>() {
@Override
public void acceptSuccess(final Iterable<Person> people) {
dumpPeople(people);
}
@Override
public void acceptFailure(final List<ApiResponseError> errors) {
dumpErrors(errors);
}
};
Stream.of(responses)
.forEach(response -> response.accept(handler));
}
private static void useSemiCallbackApproach(final ApiResponse<Iterable<Person>>... responses) {
System.out.println("<SEMI CALLBACKS>");
Stream.of(responses)
.forEach(response -> {
final Iterable<Person> people = response.acceptOrNull(Q43113283::dumpErrors);
if ( people != null ) {
dumpPeople(people);
}
});
}
private static void useNoCallbackApproach(final ApiResponse<Iterable<Person>>... responses) {
System.out.println("<NO CALLBACKS>");
Stream.of(responses)
.forEach(response -> {
final Iterable<Person> people = response.acceptOrNull();
if ( people != null ) {
dumpPeople(people);
}
});
}
private static void dumpPeople(final Iterable<Person> people) {
for ( final Person person : people ) {
System.out.println(person.name + " (" + person.age + ")");
}
}
private static void dumpErrors(final Iterable<ApiResponseError> errors) {
for ( final ApiResponseError error : errors ) {
System.err.println("ERROR: " + error.code + " " + error.message);
}
}
}
上面的代码会产生:
约翰 (21)
莎拉 (32)
错误:1001 发生故障
约翰 (21)
莎拉 (32)
错误:1001 发生故障
约翰 (21)
莎拉 (32)