问题
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;
回答1:
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}]
回答2:
You could use this website to generate the java object for you http://www.jsonschema2pojo.org/ just put the json response and choose Json for Source type and Gson for Annotation style.
and copy generated java class to your application and use it for the retrofit response .
回答3:
The problem which you have here is that for the same json field you have different types. So the first time you are getting a JSON object and the second time a JSON array and this obviously will crash as you strictly defined to be parsed as an array (List).
You need to handle this case dynamically by your side or ask by the API guys to fix the bad data structure which seems you are getting back (except if it's on purpose like that).
To understand better the JSON types read this http://www.json.org/
来源:https://stackoverflow.com/questions/43634110/retrofit-and-gson-parsing-array-element-polymorphic-objects