MalformedJsonException with Retrofit API?

36,046

Solution 1

com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) is usually thrown when there is some character(s) that malforms the JSON. Exception message itself suggest to make the deserialization more tolerant.

But I suggest you to fix your JSON and trim it from unwanted characters.

You should extend GsonConverter and override fromBody() to make Gson read from the tolerant JsonReader. Then just set it to your RestAdapter. This will attempt to use tolerant JsonReader to deserialize and then close it, if not exception is thrown.

public class LenientGsonConverter extends GsonConverter {
private Gson mGson;

public LenientGsonConverter(Gson gson) {
    super(gson);
    mGson = gson;
}

public LenientGsonConverter(Gson gson, String charset) {
    super(gson, charset);
    mGson = gson;
}

@Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
    boolean willCloseStream = false; // try to close the stream, if there is no exception thrown using tolerant  JsonReader
    try {
        JsonReader jsonReader = new JsonReader(new InputStreamReader(body.in()));
        jsonReader.setLenient(true);
        Object o = mGson.fromJson(jsonReader,type);
        willCloseStream = true;
        return o;
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if(willCloseStream) {
            closeStream(body);
        }
    }

    return super.fromBody(body, type);
}

private void closeStream(TypedInput body){
        try {
            InputStream in = body.in();
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

}

Solution 2

Seems its changed slightly with Retrofit 2.0

Here's how I did it:

 Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://whatever.com")
        .addConverterFactory(LenientGsonConverterFactory.create(gson))
        .build();

A new lenient gson factory:

public final class LenientGsonConverterFactory extends Converter.Factory {

        /**
         * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
         * decoding from JSON (when no charset is specified by a header) will use UTF-8.
         */
        public static LenientGsonConverterFactory create() {
            return create(new Gson());
        }

        /**
         * Create an instance using {@code gson} for conversion. Encoding to JSON and
         * decoding from JSON (when no charset is specified by a header) will use UTF-8.
         */
        public static LenientGsonConverterFactory create(Gson gson) {
            return new LenientGsonConverterFactory(gson);
        }

        private final Gson gson;

        private LenientGsonConverterFactory(Gson gson) {
            if (gson == null) throw new NullPointerException("gson == null");
            this.gson = gson;
        }

        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                                Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new LenientGsonResponseBodyConverter<>(gson, adapter);
        }

        @Override
        public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                              Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
            return new LenientGsonRequestBodyConverter<>(gson, adapter);
        }

    }

Lenient parsing of responses:

    private class LenientGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
        private final Gson gson;
        private final TypeAdapter<T> adapter;

        LenientGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
            this.gson = gson;
            this.adapter = adapter;
        }

        @Override
        public T convert(ResponseBody value) throws IOException {
            JsonReader jsonReader = gson.newJsonReader(value.charStream());
            jsonReader.setLenient(true);
            try {
                return adapter.read(jsonReader);
            } finally {
                value.close();
            }
        }
    }

Lenient creation of requests:

   private class LenientGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
        private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
        private static final Charset UTF_8 = Charset.forName("UTF-8");

        private final Gson gson;
        private final TypeAdapter<T> adapter;

        LenientGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
            this.gson = gson;
            this.adapter = adapter;
        }

        @Override
        public RequestBody convert(T value) throws IOException {
            Buffer buffer = new Buffer();
            Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
            JsonWriter jsonWriter = gson.newJsonWriter(writer);
            jsonWriter.setLenient(true);
            adapter.write(jsonWriter, value);
            jsonWriter.close();
            return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
        }
    }

I just copied the Retrofit source code and added a line to the request and the response converters jsonWriter.setLenient(true);


Or even easier:

    Gson gson = new GsonBuilder()
        .setLenient()
        .create();

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://whatever.com")
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build(); 

Solution 3

 RestAdapter adapterRfqPost = new RestAdapter.Builder()
                                    .setEndpoint(Constants.ENDPOINT)
                                            `enter code here`.setConverter(new ConstantsMethods.StringConverter())
                                    .build();

public static class StringConverter implements Converter {
        @Override
        public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
            String text = null;
            try {
                text = fromStream(typedInput.in());
            } catch (IOException ignored) {/*NOP*/ }
            return text;
        }

        @Override
        public TypedOutput toBody(Object o) {
            return null;
        }

        public static String fromStream(InputStream in) throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            StringBuilder out = new StringBuilder();
            String newLine = System.getProperty("line.separator");
            String line;
            while ((line = reader.readLine()) != null) {
                out.append(line);
                out.append(newLine);
            }
            return out.toString();
        }
    }
Share:
36,046
FernandoPaiva
Author by

FernandoPaiva

I'm from Brazil

Updated on April 25, 2021

Comments

  • FernandoPaiva
    FernandoPaiva about 3 years

    I need send a json to my webservice, json is:

    {
        "Sala": {
            "usuario": "%@",
            "adversario": "%@",
            "atualizacao": "%@",
            "device": "%@",
            "device_tipo": "ios"
        }
    }
    

    . I'm trying do it using Retrofit API 1.8. When I try send the post throws an exception.

    Exception:

    com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 7 path $
    

    I'm trying this

    public class ChatObject {
        private String usuario;
        private String adversario;
        private String atualizacao;
        private String email;
        private String device;
        private String device_tipo;
    

    Retrofit Interface

    @POST("/WsChat/interacao.json")
        public void onReceiveMessage(@Body ChatObject obj,
                                     Callback<JsonElement> response);
    

    Implements

    public void receiveMessage(){
        ///{\"Sala\":{\"usuario\":\"%@\",\"adversario\":\"%@\",\"atualizacao\":\"%@\",\"device\":\"%@\",\"device_tipo\":\"ios\"}}
        ChatObject chatObject = new ChatObject(BatalhaConfigs.USUARIO_EMAIL,
                                               BatalhaConfigs.ADVERSARIO_EMAIL,
                                               new Date().toString(),
                                               BatalhaConfigs.USUARIO_EMAIL,
                                               AndroidReturnId.getAndroidId(),
                                               "android");
    
        RestAdapter adapter = new RestAdapter.Builder()
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setRequestInterceptor(new CustomRequestInterceptor())
                .setEndpoint(END_POINT)
                .build();
        ChatListener listener = adapter.create(ChatListener.class);
        listener.onReceiveMessage(chatObject, new Callback<JsonElement>() {
            @Override
            public void success(JsonElement jsonElement, retrofit.client.Response response) {
                Log.i("JSON ELEMENT->", jsonElement.toString());
            }
    
            @Override
            public void failure(RetrofitError error) {
                Log.i("FALHOU->", error.getLocalizedMessage());
    
            }
        });
    }
    
  • Girish Patel
    Girish Patel about 9 years
    Nikola,,,How to call this function means,,,, .setConverter(new LenientGsonConverter(gson))
  • Nikola Despotoski
    Nikola Despotoski about 9 years
    You call this when you create your RestAdapter.
  • Girish Patel
    Girish Patel about 9 years
    Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDE‌​RSCORES) .registerTypeAdapter(Date.class, new DateTypeAdapter()) .create(); RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(urlString) .setConverter(new LenientGsonConverter(gson)) .build(); This is not working
  • Girish Patel
    Girish Patel about 9 years
    Yes...But i am not able to getting response
  • Nikola Despotoski
    Nikola Despotoski about 9 years
    This does not make GSON lient.
  • ugali soft
    ugali soft almost 8 years
    i got this error can not resolve setLenient(), in 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
  • Blundell
    Blundell almost 8 years
    @ZachariaMwalwama latest version is 2.0.2 search.maven.org/#search%7Cga%7C1%7Cconverter-gson
  • Tot Zam
    Tot Zam about 7 years
    Can you add some explanation to this answer? How does this code solve the problem? What is it changing?
  • Mehul Tank
    Mehul Tank over 5 years
    my app is not compile even i am getting same isuue