简体   繁体   中英

RETROFIT how to parse this response

I'm trying to parse this json response yahoo yql with Retrofit,but the problem is that the response begins(as you can see in the link above) with the following characters: "finance_charts_json_callback(".

Therefore i get the following error: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ .

Is posible to parse this json file with Retrofit? Thanks in advance

It returns a json that's invoked by a callback function(jsonp). Strip out the function wrapper and parse it or invoke an endpoint that's not jsonp based if one available.

Update1:

The following is a sample of how we can convert the jsonp response to json using regex:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by jamesanto on 12/22/15.
 */
public class JsonpParser {

    private static final Pattern JSONP = Pattern.compile("(?s)\\w+\\((.*)\\).*");

    public static String jsonpToJson(String jsonStr) {
        Matcher matcher = JSONP.matcher(jsonStr);
        if(matcher.find()) {
            return matcher.group(1);
        } else {
            throw new IllegalArgumentException("Unknown jsonp format");
        }
    }

    public static void main(String[] args) {
        String sampleJson = "finance_charts_json_callback({\n" +
                "    \"base\": \"cmc stations\",\n" +
                "    \"clouds\": {\n" +
                "        \"all\": 75\n" +
                "    },\n" +
                "    \"cod\": 200,\n" +
                "    \"coord\": {\n" +
                "        \"lat\": 51.51,\n" +
                "        \"lon\": -0.13\n" +
                "    },\n" +
                "    \"dt\": 1450807548,\n" +
                "    \"id\": 2643743,\n" +
                "    \"main\": {\n" +
                "        \"humidity\": 82,\n" +
                "        \"pressure\": 1011,\n" +
                "        \"temp\": 286.94,\n" +
                "        \"temp_max\": 287.59,\n" +
                "        \"temp_min\": 286.15\n" +
                "    },\n" +
                "    \"name\": \"London\",\n" +
                "    \"sys\": {\n" +
                "        \"country\": \"GB\",\n" +
                "        \"id\": 5091,\n" +
                "        \"message\": 0.0136,\n" +
                "        \"sunrise\": 1450771468,\n" +
                "        \"sunset\": 1450799652,\n" +
                "        \"type\": 1\n" +
                "    },\n" +
                "    \"weather\": [\n" +
                "        {\n" +
                "            \"description\": \"light rain\",\n" +
                "            \"icon\": \"10n\",\n" +
                "            \"id\": 500,\n" +
                "            \"main\": \"Rain\"\n" +
                "        },\n" +
                "        {\n" +
                "            \"description\": \"light intensity drizzle rain\",\n" +
                "            \"icon\": \"09n\",\n" +
                "            \"id\": 310,\n" +
                "            \"main\": \"Drizzle\"\n" +
                "        }\n" +
                "    ],\n" +
                "    \"wind\": {\n" +
                "        \"deg\": 210,\n" +
                "        \"gust\": 14.9,\n" +
                "        \"speed\": 9.8\n" +
                "    }\n" +
                "});";


        String json = jsonpToJson(sampleJson);
        System.out.println(json);
    }
}

Update 2:

I have extended the existing GsonConverterFactory to support jsonp.

//JsonpGsonResponseBodyConverter.java

package retrofit;

import com.google.gson.Gson;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;

final class JsonpGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final Type type;

  JsonpGsonResponseBodyConverter(Gson gson, Type type) {
    this.gson = gson;
    this.type = type;
  }

  private static String readerToString(Reader reader) throws IOException {
    StringBuilder builder = new StringBuilder();
    int charsRead = -1;
    char[] chars = new char[100];
    do{
      charsRead = reader.read(chars,0,chars.length);
      //if we have valid chars, append them to end of string.
      if(charsRead>0)
        builder.append(chars,0,charsRead);
    }while(charsRead>0);
    return builder.toString();
  }

  @Override public T convert(ResponseBody value) throws IOException {
    Reader reader = value.charStream();
    try {
      String jsonp = readerToString(reader);
      String json = JsonpParser.jsonpToJson(jsonp);
      return gson.fromJson(json, type);
    } finally {
      Utils.closeQuietly(reader);
    }
  }
}

//JsonpGsonConverterFactory.java

package retrofit;

import com.google.gson.Gson;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.ResponseBody;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

/**
 * A {@linkplain Converter.Factory converter} which uses Gson for JSON.
 * <p>
 * Because Gson is so flexible in the types it supports, this converter assumes that it can handle
 * all types. If you are mixing JSON serialization with something else (such as protocol buffers),
 * you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance}
 * last to allow the other converters a chance to see their types.
 */
public final class JsonpGsonConverterFactory 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 JsonpGsonConverterFactory 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 JsonpGsonConverterFactory create(Gson gson) {
    return new JsonpGsonConverterFactory(gson);
  }

  private final Gson gson;

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

  @Override
  public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) {
    return new JsonpGsonResponseBodyConverter<>(gson, type);
  }

  @Override public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) {
    return new GsonRequestBodyConverter<>(gson, type);
  }
}

Now while building the service, register the above converter to retrofit as converter like below:

Retrofit retrofit = new Retrofit.Builder()
                    .setEndpoint("<yahoo api url>").setConverter(JsonpGsonConverterFactory.create())
                    .build();

Update 3:

The class "GsonRequestBodyConverter" already comes with the following dependency, but I am adding it here for the sake of completeness:

"com.squareup.retrofit" % "converter-gson" % "2.0.0-beta2"

package retrofit;

import com.google.gson.Gson;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.RequestBody;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import okio.Buffer;

final class GsonRequestBodyConverter<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 Type type;

  GsonRequestBodyConverter(Gson gson, Type type) {
    this.gson = gson;
    this.type = type;
  }

  @Override public RequestBody convert(T value) throws IOException {
    Buffer buffer = new Buffer();
    Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
    try {
      gson.toJson(value, type, writer);
      writer.flush();
    } catch (IOException e) {
      throw new AssertionError(e); // Writing to Buffer does no I/O.
    }
    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
  }
}

And finally the missing piece 'Utils':

package retrofit;

import java.io.Closeable;
import java.io.IOException;

/**
 * Created by jamesanto on 12/23/15.
 */
public final class Utils {
    static void closeQuietly(Closeable closeable) {
        if (closeable == null) return;
        try {
            closeable.close();
        } catch (IOException ignored) {
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM