用于Array或Object的Json响应分析器

用于Array或Object的Json响应分析器

本文介绍了用于Array或Object的Json响应分析器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 其中一个端点返回一个 array 对象: [ {name:John,age:21 }, {name:Sarah, age:32 },] 但是,错误模式对于API中的所有端点是json object 而不是数组。 errors:[ {code:1001,message:东西炸毁了} ] $ b 在POJO中建模时会出现问题。由于错误模式对所有API端点都是常见的,因此我决定使用抽象的 ApiResponse 类,它只映射错误属性 public abstract class ApiResponse { @SerializedName(errors) List< ApiResponseError>错误; } public class ApiResponseError { @SerializedName(code) public Integer code; @SerializedName(message) public String message; $ b现在我想从 ApiResponse 有错误映射免费和POJO每个API端点响应。然而,这个响应的顶层json对象是一个数组(如果服务器成功执行请求),所以我不能创建一个新的类来映射它,就像我想要的那样。 我决定继续创建一个扩展 ApiResponse 的类: public class ApiResponsePerson extends ApiResponse { List< Person>者; $ b 实现了一个自定义的反序列化器来正确解析json,具体取决于顶级对象,并将其设置为以下类的正确字段: public class DeserializerApiResponsePerson实现JsonDeserializer< ApiResponsePerson> { @Override public ApiResponse deserialize(JsonElement json,Type typeOfT,JsonDeserializationContext context)throws JsonParseException { ApiResponsePerson response = new ApiResponsePerson(); if(json.isJsonArray()){ Type personType = new TypeToken< List< Person>>(){} .getType(); response.persons = context.deserialize(json,personType); 返回响应; } if(json.isJsonObject()){ JsonElement errorJson = json.getAsJsonObject()。get(errors); Type errorsType = new TypeToken< List< ApiResponseError>>(){} .getType(); response.errors = context.deserialize(errorJson,errorsType); 返回响应; } 抛出新的JsonParseException('ApiResponse意外的Json'); 然后我将其添加到Gson中 Gson gson = new GsonBuilder() .registerTypeAdapter(ApiResponsePerson.class,new DeserializerApiResponsePerson()) .create (); 有没有什么办法可以模拟这个POJO,并让Gson识别这个结构而不必手动处理这个场景? 有没有更好的方法来完成这项工作? 我是否错过反序列化器可能失败或无法正常工作的情况? 谢谢 解决方案 API响应有时候不适合像Java这样的静态类型语言。我会说,如果您遇到问题需要使用不太方便的响应格式,则必须编写更多代码(如果您希望为您提供更方便的)。在大多数情况下,Gson可以在这种情况下提供帮助,但不是免费的。没有。 Gson不会混合不同结构的对象,所以你仍然必须告诉它你的意图。我猜是的,无论是对响应进行建模还是实现解析这些响应的方式。它的响应格式与所有的反序列化程序一样敏感,所以总的来说它足够好,但可以改进。首先,让我们考虑你可以只有两种情况:常规回应和错误。这是一个经典案例,可以这样建模: 抽象类ApiResponse< T> ; { //一堆受保护的方法,不需要接口,因为我们正在考虑它是一个值类型,我们不想公开它们中的任何一个 protected abstract boolean isSuccessful() ; 保护抽象T getData()抛出UnsupportedOperationException; protected abstract List< ApiResponseError> getErrors()抛出UnsupportedOperationException; //由于我们可以自己覆盖所有的两种情况,所以让他们都在这个类中 private ApiResponse(){} static < T> ApiResponse< T>成功(最终T数据){返回新的SucceededApiResponse<>(数据); } static< T> ApiResponse< T>失败(final List< ApiResponseError>错误){ @SuppressWarnings(unchecked) final ApiResponse< T> castApiResponse =(ApiResponse< T>)new FailedApiResponse(errors); 返回castApiResponse; } //尽管这三个受保护的方法在技术上可以是公开的,但我们将其封装为 final void accept(final IApiResponseConsumer<?super T> consumer){ if(isSuccessful()){ consumer.acceptSuccess(getData()); } else { consumer.acceptFailure(getErrors()); $ b $ //创建一些友好的返回接受方法 final T acceptOrNull(){ if(!isSuccessful() ){返回null; } return getData(); } final T acceptOrNull(final Consumer< ;? super List< ApiResponseError>> errorsConsumer){ if(!isSuccessful()){ errorsConsumer.accept (getErrors()); 返回null; } return getData(); } private static final class SucceededApiResponse< T> 扩展了ApiResponse< T> { 私人最终T数据; 私人SucceededApiResponse(最终T数据){ this.data = data; } @Override protected boolean isSuccessful(){ return true; } @Override 保护T getData(){返回数据; } @Override 保护列表< ApiResponseError> getErrors()抛出UnsupportedOperationException { throw new UnsupportedOperationException(); } } private static final class FailedApiResponse extends ApiResponse< Void> { private final List< ApiResponseError>错误; private FailedApiResponse(final List< ApiResponseError>错误){ this.errors = errors; } @Override protected boolean isSuccessful(){ return false; } @Override 保护列表< ApiResponseError> getErrors(){返回错误; } @Override protected void getData() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } } } interface IApiResponseConsumer< T> { void acceptSuccess(T data); void acceptFailure(List< ApiResponseError>错误); $ p 一个简单的映射错误: final class ApiResponseError { //由于传入的DTO是大多数只读数据包 - 大多数情况下,即使getter也可能是这里的噪音 // Gson可以很容易地剥离最终修饰符 //然而,原始值由javac内联,因此我们使用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); $ b $ p $ b 第二个组件是一个特殊类型的适配器,用来告诉Gson API反应必须反序列化。注意类型适配器不像 JsonSerializer 和 JsonDeserializer 以不需要整个JSON模型( JsonElement )存储在内存中,因此您可以节省内存并提高大型JSON文档的性能。 final class ApiResponseTypeAdapterFactory 实现TypeAdapterFactory { //无状态,因此可以实例化一次 private static final TypeAdapterFactory apiResponseTypeAdapterFactory = new ApiResponseTypeAdapterFactory(); //类型标记是有效的值类型,可以根据参数化实例化一次 private static final TypeToken< List< ApiResponseError>> apiResponseErrorsType = new TypeToken< List< ApiResponseError>>(){}; $ b $ private ApiResponseTypeAdapterFactory(){} static TypeAdapterFactory getApiResponseTypeAdapterFactory(){ return apiResponseTypeAdapterFactory; } @Override public< T> TypeAdapter< T> create(final Gson gson,final TypeToken< T> typeToken){ //是否ApiResponse,我们可以处理的类? if(ApiResponse.class.isAssignableFrom(typeToken.getRawType())){ //尝试解析其参数化 final类型typeParameter = getTypeParameter0(typeToken.getType()); //向Gson询问成功和失败类型适配器使用下游解析器 final TypeAdapter<?> successTypeAdapter = gson.getDelegateAdapter(this,TypeToken.get(typeParameter)); final TypeAdapter< List< ApiResponseError>> failureTypeAdapter = gson.getDelegateAdapter(this,apiResponseErrorsType); @SuppressWarnings(unchecked)最终TypeAdapter< T> castTypeAdapter =(TypeAdapter< T>)new ApiResponseTypeAdapter<>(successTypeAdapter,failureTypeAdapter); 返回castTypeAdapter; } 返回null; } private static Type getTypeParameter0(final Type type){ //这个类型是否被参数化? if(!(type of instanceof ParameterizedType)){ //不,它是原始的 return Object.class; } final ParameterizedType parameterizedType =(ParameterizedType)type; 返回parameterizedType.getActualTypeArguments()[0]; } private static final class ApiResponseTypeAdapter< T> 扩展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; $ b @Override public void write(final JsonWriter out,final ApiResponse< T> value) throws UnsupportedOperationException { throw new UnsupportedOperationException() ; } @覆盖 public ApiResponse< T> read(final JsonReader in) throws IOException { final JsonToken token = in.peek(); 开关(令牌){ case BEGIN_ARRAY: //是数组吗?假设这些响应是数组 //否则需要更复杂的解析,在某些情况下可能用JsonDeserializer替换 //因此读取下一个值(整个数组)并将其包装在API中成功状态返回成功(successTypeAdapter.read(in))的响应; case BEGIN_OBJECT: //否则它可能是一个错误对象? in.beginObject(); final String name = in.nextName(); if(!name.equals(errors)){ //让它快速失败,如果成功的响应会在这里呢? 抛出新的MalformedJsonException(预期的错误,但是+名称); } //构造失败的响应对象并终止错误对象 final ApiResponse< T> failure = failure(failureTypeAdapter.read(in)); in.endObject(); 返回失败; //风格问题,但只是为了明确地表达意图,并使IntelliJ IDEA在缺少情况下打开枚举,不在这里报告警告案例EN​​D_ARRAY:案例EN​​D_OBJECT: case NAME: case STRING: case NUMBER: case BOOLEAN: case NULL: case END_DOCUMENT: throw new MalformedJsonException(意外的令牌:+令牌); 默认值:抛出新的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 blw up) ]}; private static final Gson gson = new GsonBuilder() .registerTypeAdapterFactory(getApiResponseTypeAdapterFactory()) .create(); //假设Type实例是不可变的,所以它可能被缓存 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(response) .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); } }); } 私人静态无效dumpPeople(最终可用人数<人>人){ for(final Person person:people){ System.out.println(person .name +(+ person.age +)); (最终ApiResponseError错误:错误){ System。} } private static void dumpErrors(final Iterable< ApiResponseError> errors) err.println(ERROR:+ error.code ++ error.message); $ b 以上将产生: $ b I am writing a library to consume a Json API and I am facing a design problem when using Gson as the parsing library.One of the endpoints returns an array of objects if everything goes well like so:[ { "name": "John", "age" : 21 }, { "name": "Sarah", "age" : 32 },]However, the error schema for all the endpoints in the API is an json object instead of an array.{ "errors": [ { "code": 1001, "message": "Something blew up" } ]}The problem arises when modeling this in POJOs. Because the error schema is common for all the API endpoints, I decided to have an abstract ApiResponse class which will only map the errors attributepublic abstract class ApiResponse{ @SerializedName("errors") List<ApiResponseError> errors;}public class ApiResponseError { @SerializedName("code") public Integer code; @SerializedName("message") public String message;} Now I would like to inherit from ApiResponse to have the error mapping "for free" and a POJO per API endpoint response. However, the top level json object for this response is an array (if the server succeeds to execute the request), so I can not create a new class to map it like I would like it. I decided to still create a class extending ApiResponse:public class ApiResponsePerson extends ApiResponse { List<Person> persons;}And implemented a custom deserializer to correctly parse the json depending on the type of the top level object, and setting it to the correct field on the following class:public class DeserializerApiResponsePerson implements JsonDeserializer<ApiResponsePerson> { @Override public ApiResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { ApiResponsePerson response = new ApiResponsePerson(); if (json.isJsonArray()) { Type personType = new TypeToken<List<Person>>() {}.getType(); response.persons = context.deserialize(json, personType); return response; } if (json.isJsonObject()) { JsonElement errorJson = json.getAsJsonObject().get("errors"); Type errorsType = new TypeToken<List<ApiResponseError>>() {}.getType(); response.errors = context.deserialize(errorJson, errorsType); return response; } throw new JsonParseException("Unexpected Json for 'ApiResponse'"); }}Which I will then add to the GsonGson gson = new GsonBuilder() .registerTypeAdapter(ApiResponsePerson.class, new DeserializerApiResponsePerson()) .create();Is there any way to model this POJOs and have Gson recognize this structure without having to manually handle this scenario?Is there any better way to accomplish this? Am I missing any scenario where the deserializer might fail or not work as expected?Thanks 解决方案 Sometimes API responses do not fit statically typed languages like Java is very well. I would say that if you're facing a problem to align with a not very convenient response format, you have to write more code if you want it to be convenient for you. And in most cases Gson can help in such cases, but not for free.No. Gson does not mix objects of different structure, so you still have to tell it your intentions.I guess yes, for both modelling the response and implementing the way how such responses are parsed.It's response format sensitive like all deserializers are, so in general it's good enough, but can be improved.First off, let's consider you can have two cases only: a regular response and an error. This is a classic case, and it can be modelled like that: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);}A trivial mapping for 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;}And some values too:final class Person { final String name = null; final int age = Integer.valueOf(0);}The second component is a special type adapter to tell Gson how the API responses must be deserialized. Note that type adapter, unlike JsonSerializer and JsonDeserializer work in streaming fashion not requiring the whole JSON model (JsonElement) to be stored in memory, thus you can save memory and improve the performance for large JSON documents.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); } } }}Now, how it all can be put together. Note that the responses do not expose their internals explicitly but rather requiring consumers to accept making its privates really encapsulated. 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); } }}The code above will produce: 这篇关于用于Array或Object的Json响应分析器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
10-29 02:22