Retrofit和Gson:解析数组/元素多态对象 [英] Retrofit and Gson: parsing array/element-polymorphic objects
问题描述
parameters:{
parameter:{
Data:value
}
},
参数:{
参数:[
{
Data:value
},
{
Data:value
},
]
},
如果我调用 List< Class>
参数:
预期的BEGIN_OBJECT,但获取BEGIN_ARRAY
我需要解析参数来获取值
public class ApiClient {
public static final String BASE_URL =http:// .........;
private static Retrofit retrofit = null;
$ b public static Retrofit getClient(){
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1,TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1,TimeUnit.MINUTES)
.addInterceptor(new ServiceGenerator(Content-Type,application / json))。build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
if(retrofit == null){
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
。客户端(客户端)
.build();
}
返回改造;
}
}
public class ServiceGenerator实现Interceptor {
private String httpUsername;
私人字符串httpPassword;
public ServiceGenerator(String httpUsername,String httpPassword){
this.httpUsername = httpUsername;
this.httpPassword = httpPassword;
$ b @Override
public response拦截(链式链接)抛出IOException {
Request newRequest = chain.request()。newBuilder()
.addHeader(Authorization,getAuthorizationValue())
.build();
return chain.proceed(newRequest);
}
private String getAuthorizationValue(){
final String userAndPassword = httpUsername +:+ httpPassword;
返回Basic+ Base64.encodeToString(userAndPassword.getBytes(),Base64.NO_WRAP);
}
}
@POST(OneWay.json)
呼叫< ApiResponse> sendOneWay(@Body查询数据);
@SerializedName(FlightDetails)
public ApiResponse FlightDetails;
现在我调用一个类ApiResponse
但是如何调用
public ApiResponse航班详情; &安培; public List FlightDetails;
这只是一个非常微不足道的问题,它经常发生在具有奇怪设计选择的API上。您只需将两种格式对齐为统一格式即可:列表可以涵盖两种情况。所以,你必须实现的是一个类型适配器,它会检查这种对齐是否必要,如果该值是一个列表,则使用原始类型适配器,或者将其包含在单个元素列表中。
为简单起见,请考虑以下JSON文档:
single.json
<$
virtual:{
key-1:value-1
}
}
/ code>
multiple.json
{
virtual:[
{
key-1:value-1
},
{
key-2 :value-2
}
]
}
现在使用对齐的字段定义一个映射:
$ b
final class Response {
@JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List< Map< String,String>> virtual = null;
$ b
请注意 JsonAnnotaion
注释:这是一种告诉Gson如何读取或写入字段的方法。 AlwaysListTypeAdapterFactory
实现可能如下:
final class AlwaysListTypeAdapterFactory
实现TypeAdapterFactory {
//总是考虑将构造函数设为private
// + Gson可以实例化这个工厂本身
private AlwaysListTypeAdapterFactory(){
}
@Override
public< T> TypeAdapter< T>创建(最终的Gson gson,最终的TypeToken< T> typeToken){
//不是列表?
if(!List.class.isAssignableFrom(typeToken.getRawType())){
//不是我们可以处理的
返回null;
}
//现在只需返回一个特殊类型的适配器,它可以检测如何处理对象
@SuppressWarnings(unchecked)
final TypeAdapter< T> (TypeAdapter< T>)new AlwaysListTypeAdapter<>(
(TypeAdapter< Object>)gson.getAdapter(TypeToken.get(getTypeParameter0(typeToken.getType()))),
(TypeAdapter< List< Object>>)gson.getAdapter(typeToken)
);
返回castTypeAdapter;
//用于检测列表参数化
private static Type getTypeParameter0(final Type type){
if(!(type of instanceof ParameterizedType)) {
//是通配符还是原始类型?然后我们不能确定真正的参数化
返回Object.class;
}
//或者只是在列表< E>中解析实际的E,
final ParameterizedType parameterizedType =(ParameterizedType)type;
返回parameterizedType.getActualTypeArguments()[0];
}
private static final class AlwaysListTypeAdapter< E>
扩展TypeAdapter< List< E>> {
private final TypeAdapter< E> elementTypeAdapter;
private final TypeAdapter< List< E>> listTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter< E> elementTypeAdapter,final TypeAdapter< List< E>> listTypeAdapter){
this.elementTypeAdapter = elementTypeAdapter;
this.listTypeAdapter = listTypeAdapter;
$ b @Override
public void write(final JsonWriter out,final List< E> value)
throws IOException {
listTypeAdapter.write(out ,价值);
}
@Override
public List< E> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch(token){
case BEGIN_ARRAY:
//如果下一个标记是[,假设是一个普通列表,只是将作业委托给Gson内部
返回listTypeAdapter。读取(在);
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
//任何其他值?尽管Collections.singletonList()可能被使用,但Gson会返回可变的ArrayList实例,所以我们做...
final List< E> list = new ArrayList<>();
list.add(elementTypeAdapter.read(in));
返回列表;
案例END_ARRAY:
案例END_OBJECT:
案例名称:
案例END_DOCUMENT:
//这里可怕的事情...
抛出新的MalformedJsonException(Unexpected token:+ token +在+ in;
默认值:
//如果有一天Gson添加一个新的令牌
抛出新的AssertionError(token);
}
}
}
}
测试:
$ b
public static void main(final String .. (最后一个字符串资源:ImmutableList.of(single.json,multiple.json)){
try(final Reader reader = getPackageResourceReader(args)
throws IOException {
Q43634110.class,resource)){
final Response response = gson.fromJson(reader,Response.class);
System.out.println(resource);
System.out.println(\t+ response.virtual);
}
}
}
输出:
single.json
[{key-1 = value-1}]
multiple.json <
[{key-1 = value-1},{key-2 = value-2}]
I am getting response in a sequence:
"parameters": {
"parameter": {
"Data":"value"
}
},
"parameters":{
"parameter": [
{
"Data":"value"
},
{
"Data":"value"
},
]
},
Getting the error if I call List<Class>
parameter:
Expected BEGIN_OBJECT but getting BEGIN_ARRAY
I need to parse parameter to get values
public class ApiClient {
public static final String BASE_URL ="http://.........";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1, TimeUnit.MINUTES)
.writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.addInterceptor(new ServiceGenerator("Content-Type","application/json")).build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
}
return retrofit;
}
}
public class ServiceGenerator implements Interceptor{
private String httpUsername;
private String httpPassword;
public ServiceGenerator(String httpUsername, String httpPassword) {
this.httpUsername = httpUsername;
this.httpPassword = httpPassword;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", getAuthorizationValue())
.build();
return chain.proceed(newRequest);
}
private String getAuthorizationValue() {
final String userAndPassword = httpUsername + ":" + httpPassword;
return "Basic " + Base64.encodeToString(userAndPassword.getBytes(), Base64.NO_WRAP);
}
}
@POST("OneWay.json")
Call<ApiResponse> sendOneWay(@Body Query data);
@SerializedName("FlightDetails")
public ApiResponse FlightDetails;
Now I called a Class ApiResponse But How to call both public ApiResponse FlightDetails; & public List FlightDetails;
This is just a very trivial issue that occurs often with APIs that have weird design choices. You just have to "align" both formats to a unified form: lists can cover both cases. So, all you have to implement is a type adapter that would check if such an alignment is necessary and use either the original type adapter if the value is a list, or wrap it up in a single element list.
For simplicity, consider the following JSON documents:
single.json
{
"virtual": {
"key-1": "value-1"
}
}
multiple.json
{
"virtual": [
{
"key-1": "value-1"
},
{
"key-2": "value-2"
}
]
}
Now define a mapping with the aligned field:
final class Response {
@JsonAdapter(AlwaysListTypeAdapterFactory.class)
final List<Map<String, String>> virtual = null;
}
Note the JsonAnnotaion
annotation: this is a way to tell Gson how the field must be read or written. The AlwaysListTypeAdapterFactory
implementation might be as follows:
final class AlwaysListTypeAdapterFactory
implements TypeAdapterFactory {
// Always consider making constructors private
// + Gson can instantiate this factory itself
private AlwaysListTypeAdapterFactory() {
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Not a list?
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
// Not something we can to deal with
return null;
}
// Now just return a special type adapter that could detect how to deal with objects
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) new AlwaysListTypeAdapter<>(
(TypeAdapter<Object>) gson.getAdapter(TypeToken.get(getTypeParameter0(typeToken.getType()))),
(TypeAdapter<List<Object>>) gson.getAdapter(typeToken)
);
return castTypeAdapter;
}
// This is used to detect the list parameterization
private static Type getTypeParameter0(final Type type) {
if ( !(type instanceof ParameterizedType) ) {
// Is it a wildcard or raw type? Then we cannot determine the real parameterization
return Object.class;
}
// Or just resolve the actual E in List<E>
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
private static final class AlwaysListTypeAdapter<E>
extends TypeAdapter<List<E>> {
private final TypeAdapter<E> elementTypeAdapter;
private final TypeAdapter<List<E>> listTypeAdapter;
private AlwaysListTypeAdapter(final TypeAdapter<E> elementTypeAdapter, final TypeAdapter<List<E>> listTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
this.listTypeAdapter = listTypeAdapter;
}
@Override
public void write(final JsonWriter out, final List<E> value)
throws IOException {
listTypeAdapter.write(out, value);
}
@Override
public List<E> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case BEGIN_ARRAY:
// If the next token is [, assume is a normal list, and just delegate the job to Gson internals
return listTypeAdapter.read(in);
case BEGIN_OBJECT:
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
// Any other value? Wrap it up ourselves, but use the element type adapter
// Despite Collections.singletonList() might be used, Gson returns mutable ArrayList instances, so we do...
final List<E> list = new ArrayList<>();
list.add(elementTypeAdapter.read(in));
return list;
case END_ARRAY:
case END_OBJECT:
case NAME:
case END_DOCUMENT:
// Something terrible here...
throw new MalformedJsonException("Unexpected token: " + token + " at " + in);
default:
// If someday Gson adds a new token
throw new AssertionError(token);
}
}
}
}
The test:
public static void main(final String... args)
throws IOException {
for ( final String resource : ImmutableList.of("single.json", "multiple.json") ) {
try ( final Reader reader = getPackageResourceReader(Q43634110.class, resource) ) {
final Response response = gson.fromJson(reader, Response.class);
System.out.println(resource);
System.out.println("\t" + response.virtual);
}
}
}
Output:
single.json
[{key-1=value-1}]
multiple.json
[{key-1=value-1}, {key-2=value-2}]
这篇关于Retrofit和Gson:解析数组/元素多态对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!