I'm facing this issue on my project. I receive from api call a response like:
{
"aResponse": {
"listOfSomething": [
//here some data
]
}
}
And relative data classes are
data class ResponseClass(
val aResponse : AResponse
)
data class AResponse(
val listOfSomething : List<String>
)
Not it happen that when "listOfSomething" is empty, i receive this response:
{
"aResponse": {
"listOfSomething": ""
}
}
that throws (of course) the exception
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was STRING
How can i solve it?
You are getting this error as when there is data you get array and when no data get string which is wrong in retrofit.
If there are no data insise listOfSomething then ask backend to send empty array instead of string.
{
"aResponse": {
"listOfSomething": []
}
}
instead of
{
"aResponse": {
"listOfSomething": ""
}
}
If your json result is gonna change depends of the result, first of all your backend is doing a bad job, then you have to "hack" a little bit your app to adapt the code...
Your POJO class should be :
data class MyResponse(
val aResponse: AResponse
)
data class AResponse(
val listOfSomething: Any
)
You can declare it as Any which is not a good practise, but it's a workaround to make it work according to your backend. Is like in Java adding Object
Then you can do something in your onResponse
#Override
fun onResponse(response: Response<MyResponse>) {
if (response.isSuccess()) {
if (response.listOfSomething is String) {
//do something with the String
} else {
//do something with the List
}
}
}
First, your backend implementation is wrong.
You should not send an empty string to represent an empty array.
If you can't fix it on backend side because the API are not under your control, you can try with something like this:
public final class IgnoreStringForArrays implements JsonAdapter.Factory {
#Retention(RetentionPolicy.RUNTIME)
#JsonQualifier
public #interface IgnoreJsonArrayError {
}
#Override
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
if (annotations != null && annotations.size() > 0) {
for (Annotation annotation : annotations) {
if (annotation instanceof IgnoreJsonArrayError) {
final JsonAdapter<Object> delegate = moshi.nextAdapter(this, type, Types.nextAnnotations(annotations, IgnoreJsonArrayError.class));
return new JsonAdapter<Object>() {
#Override
public Object fromJson(JsonReader reader) throws IOException {
JsonReader.Token peek = reader.peek();
if (peek != JsonReader.Token.BEGIN_ARRAY) {
reader.skipValue();
return null;
}
return delegate.fromJson(reader);
}
#Override
public void toJson(JsonWriter writer, Object value) throws IOException {
delegate.toJson(writer, value);
}
};
}
}
}
return null;
}
}
like suggested here: https://github.com/square/moshi/issues/295#issuecomment-299636385
And then annotate your listOfSomething with: IgnoreJsonArrayError annotation
Related
I'm struggling to figure out how to add a CustomTypeAdapter to my ApolloClient.
For a mutation, our server is expecting json input. The corresponding iOS app is passing in a json string.
When I pass in a string I get a message asking if I've forgotten to add a customtype.
Here is my attempt:
build.gradle
apollo {
useSemanticNaming = true
customTypeMapping['ISOTime'] = "java.util.Date"
customTypeMapping['JSON'] = "java.lang.JSONObject"
}
Here's where it is instantiated.
val jsonCustomTypeAdapter = object : CustomTypeAdapter<JSONObject> {
override fun decode(value: CustomTypeValue<*>): JSONObject {
return JSONObject()
}
override fun encode(value: JSONObject): CustomTypeValue<*> {
return CustomTypeValue.GraphQLJsonString(value.toString())
}
}
mApolloClient = ApolloClient
.builder()
.serverUrl(baseUrl)
.addCustomTypeAdapter(jsonCustomTypeAdapter)
.normalizedCache(cacheFactory, CacheKeyResolver.DEFAULT)
.httpCache(ApolloHttpCache(cacheStore, null))
.okHttpClient(mHttpClient)
.build()
It seems Apollo has generated a CustomType enum implementing ScalarType but I'm not sure if or how to use it.
#Generated("Apollo GraphQL")
public enum CustomType implements ScalarType {
JSON {
#Override
public String typeName() {
return "Json";
}
#Override
public Class javaType() {
return Object.class;
}
},
ID {
#Override
public String typeName() {
return "ID";
}
#Override
public Class javaType() {
return String.class;
}
}
}
I've attempted the example given on the apolloandroid github but it hasn't worked for me and it is in Java and after I convert it to Kotlin, it doesn't compile.
Any hints or direction to persue would be appreciated. Thanks.
It turns out Apollo had auto generated the type and all I had to do was declare it correctly in the build.gradle. I didn't need to add any custom type adapter to the ApolloClient.
NOTE: The type Json was provided by our server.
apollo {
useSemanticNaming = true
customTypeMapping['ISOTime'] = "java.util.Date"
customTypeMapping['Json'] = "java.lang.String"
}
I'm copying the Google's code from their repository of samples implementing Android Architecture Components, slowly adapting to the needs of the app I have in mind, using the code as a base. I have reached already a part where for me is displaying an error and I can't understand why. This is the code block:
data class ApiSuccessResponse<T>(val responseBody: T) : ApiResponse<T>() {
constructor(body: T) : this (responseBody = body)
}
The error message underlying is
Conflicting overloads: public constructor ApiSuccessResponse(body: T) defined in com.example.rxe.api.ApiSuccessResponse, public constructor ApiSuccessResponse(responseBody: T) defined in com.example.rxe.api.ApiSuccessResponse
Here's where I call ApiSuccessResponse, just like in the sample code:
sealed class ApiResponse<T> {
companion object {
fun <T> create(response: Response<T>): ApiResponse<T> {
return if (response.isSuccessful) {
val responseBody = response.body()
if (responseBody == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(body = responseBody)
}
} else {
val error = response.errorBody()?.string()
val message = if (error.isNullOrEmpty()) {
response.message()
} else {
error
}
ApiErrorResponse(errorMessage = message ?: "Unknown error")
}
}
}
}
Something might have changed since the sample has been written. But if I rename the variable to body instead of responseBody, the same error will underline the call for the class ApiSuccessResponse.
You don't seem to understand how constructors work in Kotlin. This is the code you tried to copy:
data class ApiSuccessResponse<T>(
val body: T,
val links: Map<String, String>
) : ApiResponse<T>() {
constructor(body: T, linkHeader: String?) : this(
body = body,
links = linkHeader?.extractLinks() ?: emptyMap()
)
//.... rest of body
}
It has two constructors:
Primary constructor: ApiSuccessResponse(body: T, links: Map<String, String>)
Secondary constructor: ApiSuccessResponse(body: T, linkHeader: String?) (which extracts map of links from String and passes it as links into primary).
What you have is:
Primary constructor: ApiSuccessResponse(resposebody: T)
Secondary constructor: ApiSuccessResponse(body: T) (which tries to call primary constructor 1:1, but it just clashes due to identical signature)
If you don't need secondary constructor You should just delete it entirely.
Android Studio 3.1 RC 2
kotlin 1.2.30
Signature for the fetchMessage
Single<Response> fetchMessage(final String messageId);
The kotlin code that I am trying to convert to Java. However, I am not sure where the returns are? As I am new to kotlin and lambda.
private fun getMessage(messageId: String): Observable<State> {
return repository
.fetchMessage(messageId)
.flatMap {
Single.fromCallable<State>({
update(messageId, it, State.COMPLETED)
State.COMPLETED
})
}
.toObservable()
}
This is my attempt at trying to convert it. However, the compiler complains of a missing return.
public Observable<TranslationChatState> translate(String messageId) {
return repository
.fetchMessage(messageId)
.flatMap(new Func1 <Response, Single<State >>() {
#Override
public Single<State> call(final Response response) {
Single.fromCallable(new Callable<State>() {
#Override
public State call() {
update(messageId, response, State.COMPLETED);
return State.COMPLETED;
}
});
} /* complains about no return here */
})
.toObservable();
}
Many thanks for any suggestions,
because it is missing the return statement. It should be
public Single<State> call(final Response response) {
return Single.fromCallable(new Callable<State>() {
private Observable< SimpleResource > resource;
return resource.map(new Function<SimpleResource, Flowable<Data>>() {
#Override
public Flowable< Data > apply(SimpleResource resource) throws Exception {
return resource.data().toFlowable();
}
});
Single<Data> data();
I need to have Flowable but my result is Observable>
Since you mentioned that data() returns a Single, you need to transform all of the single streams into one large stream. To transform streams into streams, we generally use the flatmap operator:
resource.flatMapSingle(
new Function<SimpleResource, Single<Data>>() {
#Override
public Single<Data> apply(SimpleResource resource) throws Exception {
return resource.data();
}
}
).toFlowable(BackpressureStrategy.BUFFER);
What you are doing wrong is applying .toFlowable at not the right spot.
Observable.fromCallable { 1 }.map {
it * 2
}.toFlowable(BackpressureStrategy.BUFFER)
If you have different data type returned by data (sorry for Kotlin, but the concept is the same)
data class A(
val data: Single<Int>
) {
constructor() : this(data = Single.fromCallable { 1 })
}
val result: Flowable<Int> = Flowable
.fromCallable {
A()
}
.flatMap {
it.data.toFlowable()
}
I'm trying to find a way to use AutoValue to deserialize a JSON obj to a Java class (which is also Parcelable)
The JSON response is usually in the form of,
{
"varKey1": {
"occupations": [
{
"value": "val1",
"name": "name1"
},
{
"value": "val2",
"name": "name2"
}
]
},
"varKey2": {
"occupations": [
{
"value": "val1",
"name": "name1"
},
{
"value": "val2",
"name": "name2"
}
]
}
}
where varKey1 and varKey2 are strings that are not fixed/predefined so could have any value.
I'm having difficulty figuring out what the typeAdapter for this should look like though with AutoValue Gson, and any help with this would be greatly appreciated.
As far I understand how AutoValue and AutoValue: Gson Extension work, you cannot deserialize Map<String, List<Obj>> from the given JSON by using these tools only, since they are just simple source code generators. The latter even states that creates a simple Gson TypeAdapterFactory for each AutoValue annotated object.
Taking the given JSON and Map<String, List<Obj>> into account, you can:
... have Map<String, Wrapper> where the wrapper class contains List<Obj> (and declare a type adapter for each #AutoValue-annotated) class, thus having a dumb intermediate mediator object named Wrapper.
... implement a custom type adapter that cannot be generated by AutoValue: Gson Extension.
The 2nd option is not hard as it may sound, and can help to work around when the AutoValue extension cannot generate type adapters for other sophisticated cases.
Declare necessary type tokens
final class TypeTokens {
private TypeTokens() {
}
static final TypeToken<Map<String, List<Obj>>> mapStringToObjListTypeToken = new TypeToken<Map<String, List<Obj>>>() {
};
static final TypeToken<List<Obj>> objListTypeTypeToken = new TypeToken<List<Obj>>() {
};
}
Implement a custom type adapter factory
Gson type adapter factories are used to resolve (and bind) a particular type adapter and, if necessary, bind the adapter to Gson instances to deal with any Gson configuration.
final class CustomTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory customTypeAdapterFactory = new CustomTypeAdapterFactory();
static TypeAdapterFactory getCustomTypeAdapterFactory() {
return customTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getType().equals(mapStringToObjListTypeToken.getType()) ) {
#SuppressWarnings({ "unchecked", "rawtypes" })
final TypeAdapter<T> castTypeAdapter = (TypeAdapter) getMapStringToObjectListTypeAdapter(gson);
return castTypeAdapter;
}
return null;
}
}
Note that the only responsibility of the factory is returning a special type adapter that can skip the wrappers in your JSON. If any other type is requested to the factory, returning null is safe and lets Gson try to pick another type adapter best-match (built-ins or your configuration).
Implement the type adapter
This is actually what Auto Value: Gson Extension seems not can do. Since type adapters are essentially stream-oriented, they may look too low-level, but this is the best Gson can do because streaming is a very efficient technique, and it's also used in what Auto Value: Gson Extension generates.
final class MapStringToObjectListTypeAdapter
extends TypeAdapter<Map<String, List<Obj>>> {
private final TypeAdapter<List<Obj>> wrapperAdapter;
private MapStringToObjectListTypeAdapter(final TypeAdapter<List<Obj>> wrapperAdapter) {
this.wrapperAdapter = wrapperAdapter;
}
static TypeAdapter<Map<String, List<Obj>>> getMapStringToObjectListTypeAdapter(final Gson gson) {
return new MapStringToObjectListTypeAdapter(gson.getAdapter(objListTypeTypeToken));
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final Map<String, List<Obj>> value)
throws IOException {
if ( value == null ) {
// nulls must be written
out.nullValue();
} else {
out.beginObject();
for ( final Entry<String, List<Obj>> e : value.entrySet() ) {
out.name(e.getKey());
out.beginObject();
out.name("occupations");
wrapperAdapter.write(out, e.getValue());
out.endObject();
}
out.endObject();
}
}
#Override
public Map<String, List<Obj>> read(final JsonReader in)
throws IOException {
// if there's JSON null, then just return nothing
if ( in.peek() == NULL ) {
return null;
}
// or read the map
final Map<String, List<Obj>> result = new LinkedHashMap<>();
// expect the { token
in.beginObject();
// and read recursively until } is occurred
while ( in.peek() != END_OBJECT ) {
// this is the top-most level where varKey# occur
final String key = in.nextName();
in.beginObject();
while ( in.peek() != END_OBJECT ) {
final String wrapperName = in.nextName();
switch ( wrapperName ) {
case "occupations":
// if this is the "occupations" property, delegate the parsing to an underlying type adapter
result.put(key, wrapperAdapter.read(in));
break;
default:
// or just skip the value (or throw an exception, up to you)
in.skipValue();
break;
}
}
in.endObject();
}
in.endObject();
return result;
}
}
Auto Value-generated factory
Unlike the custom type adapter factory, some of Gson type adapter factories are already generated and can deal with abstract classes like what Obj is (at least in your source code, not the generated one).
#GsonTypeAdapterFactory
abstract class GeneratedTypeAdapterFactory
implements TypeAdapterFactory {
public static TypeAdapterFactory getGeneratedTypeAdapterFactory() {
return new AutoValueGson_GeneratedTypeAdapterFactory();
}
}
How it's used
private static void dump(final Map<?, ?> map) {
for ( final Entry<?, ?> e : map.entrySet() ) {
out.print(e.getKey());
out.print(" => ");
out.println(e.getValue());
}
}
...
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getGeneratedTypeAdapterFactory())
.registerTypeAdapterFactory(getCustomTypeAdapterFactory())
.create();
dump(gson.fromJson(json, mapStringToObjListTypeToken.getType()));
gives the following output:
varKey1 => [Obj{name=name1, value=val1}, Obj{name=name2, value=val2}]
varKey2 => [Obj{name=name1, value=val1}, Obj{name=name2, value=val2}]
If you use wrappers only from the first option, then the output is as follows:
varKey1 => Wrapper{occupations=[Obj{name=name1, value=val1}, Obj{name=name2, value=val2}]}
varKey2 => Wrapper{occupations=[Obj{name=name1, value=val1}, Obj{name=name2, value=val2}]}