有时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 pre = 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":[{"pre":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.pre + " " + error.message); } }}
上面的代码将产生:
John(21)
Sarah(32)
错误:1001炸毁了
John(21)
Sarah(32)
错误:1001炸毁了
John(21)
Sarah(32)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)