我正在编写一个使用Json API的库,并且在使用Gson作为解析库时遇到了设计问题。
array
如果一切顺利,则端点之一将返回对象:
[
{
"name": "John",
"age" : 21
},
{
"name": "Sarah",
"age" : 32
},
]
但是,API中所有端点的错误模式都是json object
而不是数组。
{
"errors": [
{
"code": 1001,
"message": "Something blew up"
}
]
}
在POJO中对此建模时会出现问题。因为错误模式对于所有API端点都是通用的,所以我决定有一个ApiResponse
仅映射errors属性的抽象类。
public abstract class ApiResponse{
@SerializedName("errors")
List<ApiResponseError> errors;
}
public class ApiResponseError {
@SerializedName("code")
public Integer code;
@SerializedName("message")
public String message;
}
现在,我想继承自此,ApiResponse
以获取“免费”错误映射和每个API端点响应的POJO。但是,此响应的顶级json对象是一个数组(如果服务器成功执行了请求),因此我无法创建一个新类来映射它,就像我想要的那样。
我决定仍然创建一个扩展类ApiResponse
:
public class ApiResponsePerson extends ApiResponse {
List<Person> persons;
}
并实现了一个自定义反序列化器,以根据顶级对象的类型正确解析json,并将其设置为以下类的正确字段:
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'");
}
}
然后我将其添加到Gson
Gson gson = new GsonBuilder()
.registerTypeAdapter(ApiResponsePerson.class, new DeserializerApiResponsePerson())
.create();
有什么方法可以对此POJO进行建模,并使Gson无需手动处理这种情况就能识别出这种结构?有没有更好的方法可以做到这一点?我是否错过了解串器可能会失败或无法按预期工作的任何情况?
谢谢
有时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);
}
}
}
上面的代码将产生:
John(21)
Sarah(32)
错误:1001炸毁了
John(21)
Sarah(32)
错误:1001炸毁了
John(21)
Sarah(32)
问题内容: 我试图从JSON数组中获取每个JSON对象。我通过HTTP发布获得此数据。 我知道我的数据是什么样的: 我的示例代码和结构如下所示: 我不确定如何遍历JSON数组并获取JSON对象,然后仅使用JSON对象。 问题答案: 试试这个作为您的结构, 您的名称不正确,顶层名称也不正确。解码为a之后,您可以遍历切片以获取每个切片
问题内容: 我正在尝试从FlickR解析JSON响应,但是遇到了困难。我已经进行了一些测试,并且收到200的响应代码,并且能够使用日志记录拦截器查看实际的JSON。 但是,当我尝试访问JSON对象时,我收到了一个空指针。我觉得这与我的映射/ JSON到POJO有关: 相片: 照片: JSON响应: LOGCAT 72行 问题答案: 您的课程不适合服务器 响应, 因此您的身体为 Null 。您的模型
问题内容: 我有以下格式的对象数组: 我想要得到的是C#中的代码,其中一个对象包含一个json对象中的所有数据。问题是,我可以 不 使一个类与此对象喜欢这里的属性: 因为我每次都会得到不同的结果,但是我知道它总是一个对象数组。有人知道我如何设法取回一系列对象? 编辑 我必须通过将该对象传递给powershell 。因此,输出仅应是中的对象。 问题答案: 像这样使用newtonsoft:
我正在尝试解析没有数组名的json结果。以下是我的json回复: 如何使用库?
来自API的Im有两个不同的响应。然后我判断对象,但是我如何将消息的内容返回到字符串呢?下面的示例返回Exception: Json响应: Json响应: 模型:
问题内容: 我有一个JSON,它可以是单个对象或相同对象的数组。有没有一种方法可以使用Gson解析此数据,从而区分单个对象还是数组? 我目前唯一的解决方案是手动解析json并用try catch包围它。首先,我将尝试将其解析为单个对象,如果失败,它将引发异常,然后尝试将其解析为数组。 我不想手动解析它……那将使我永远。这是正在发生的事情的想法。 这是可以是数组或单个对象的对象。 然后在与json响