-1

How to parse this in Retrofit (GSON) ?

["football",["football","football skills","football vines","football fails","football manager 2017","football challenge","football respect","football manager 2017 download","football factory","football daily"]]

It's a array with one string and another array. No objects, just arrays. How to represent that in model?

This is server: http://suggestqueries.google.com/complete/search?client=firefox&ds=yt&q=Query

I tried to use JSON to POJO but it returns nothing.

vanste25
  • 1,754
  • 14
  • 39
  • are you controlling the server too? because in my opinion, this json is somehow "wrong". You could try something like: `{type: "football", list: ["football", "fb skills",.....]}` – MoQ93 Feb 07 '17 at 23:32
  • Possible duplicate of [Using GSON in Android to parse a complex JSON object](http://stackoverflow.com/questions/4556230/using-gson-in-android-to-parse-a-complex-json-object) – David Rawson Feb 07 '17 at 23:33
  • I don't... JSON is valid, but for gson...hm This is the server: http://suggestqueries.google.com/complete/search?client=firefox&ds=yt&q=Query – vanste25 Feb 07 '17 at 23:34
  • @DavidRawson It's not. In my case, there are no objects. Just array in array. – vanste25 Feb 07 '17 at 23:35

2 Answers2

0

@MoQ is right, the json is wrong in that it's not an object. You need to wrap it inside one, like {"list":["football",["football","football skills","football vines","football fails","football manager 2017","football challenge","football respect","football manager 2017 download","football factory","football daily"]]}

It's called JavaScript Object Notation.

Micha
  • 396
  • 2
  • 8
  • I agree with you, but server returns this, so I am trying to solve this somehow. Server: http://suggestqueries.google.com/complete/search?client=firefox&ds=yt&q=Query – vanste25 Feb 07 '17 at 23:37
  • Okay, in that case you should rephrase the question. You're not getting json, but want the parser to be lenient enough to deserialize it into an object. – Micha Feb 07 '17 at 23:40
  • But every json validator says that it's valid json. What is it, if not json? – vanste25 Feb 07 '17 at 23:42
  • Interesting. I skimmed the RFC and it seems there's nothing in there that forbids that structure. Good to know, thanks. However, how would you possibly map that to an object oriented environment? My approach would still be to transform the json before it reaches the parser. – Micha Feb 07 '17 at 23:48
  • I though that I could find some work-around. I know that it's not possible to map easily. Also, I parsed this easily by using JSONArray and it is quite simple. – vanste25 Feb 07 '17 at 23:52
  • There's http://stackoverflow.com/questions/18421674/using-gson-to-parse-a-json-array - apparently, it does work after all - just not in a way that would be supported by json2pojo (as there's no object to model the pojo after). Edit: Nvm, forgot about the gson part ._. – Micha Feb 07 '17 at 23:53
  • Thank you for your help @Micha – vanste25 Feb 07 '17 at 23:57
0

Gson is great and really allows applying custom deserialization strategies, or implementing adapters to JSON structures that are not very user-friendly. Suppose you are fine to have the following custom mapping to search query results:

final class Suggestions {

    final String query;
    final List<String> suggestions;

    Suggestions(final String query, final List<String> suggestions) {
        this.query = query;
        this.suggestions = suggestions;
    }

}

Writing a custom JsonDeserializer (consider it's just a JSON to a Java object/value tranformer) where you define how to parse the given JSON payload and convert it to a Suggestions class instance:

final class SuggestionsJsonDeserializer
        implements JsonDeserializer<Suggestions> {

    private static final JsonDeserializer<Suggestions> suggestionsJsonDeserializer = new SuggestionsJsonDeserializer();

    private static final Type listOfStringsType = new TypeToken<List<String>>() {
    }.getType();

    private SuggestionsJsonDeserializer() {
    }

    static JsonDeserializer<Suggestions> getSuggestionsJsonDeserializer() {
        return suggestionsJsonDeserializer;
    }

    @Override
    public Suggestions deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        if ( !jsonElement.isJsonArray() ) {
            throw new JsonParseException("The given JSON is not an array");
        }
        final JsonArray jsonArray = jsonElement.getAsJsonArray();
        final int length = jsonArray.size();
        if ( length != 2 ) {
            throw new JsonParseException("The given JSON array length is " + length);
        }
        final JsonElement e0 = jsonArray.get(0);
        final JsonElement e1 = jsonArray.get(1);
        final String query;
        final List<String> suggestions;
        if ( e0.isJsonPrimitive() && e1.isJsonArray() ) {
            // If the JSON array is [query, suggestions]
            query = e0.getAsJsonPrimitive().getAsString();
            suggestions = context.deserialize(e1.getAsJsonArray(), listOfStringsType);
            // e1.getAsJsonArray() call is unnecessary because the context would throw an exception if it would be not an array
            // But just make it explicit and "more symmetric" to the object destructuring around
            // Another way might be not delegating the string list parsing to context, but building a string list out of the JSON array of strings manually
        } else if ( e0.isJsonArray() && e1.isJsonPrimitive() ) {
            // If the JSON array elements are swapped like [suggestions, query]
            query = e1.getAsJsonPrimitive().getAsString();
            suggestions = context.deserialize(e0.getAsJsonArray(), listOfStringsType);
        } else {
            throw new JsonParseException("Unexpected JSON array structure");
        }
        return new Suggestions(query, suggestions);
    }

}

The next step is making Gson to be aware of your mapping and its JSON deserializer pair:

final Gson gson = new GsonBuilder()
        .registerTypeAdapter(Suggestions.class, getSuggestionsJsonDeserializer())
        .create();

Testing it without Retrofit

    final Suggestions suggestions = gson.fromJson(JSON, Suggestions.class);
    out.println(suggestions.query);
    out.println(suggestions.suggestions);

will output:

football
[football, football skills, football vines, football fails, football manager 2017, football challenge, football respect, football manager 2017 download, football factory, football daily]

In order to make Retrofit aware of your custom Gson instance, you just need to register it in Retrofit.Builder with .addConverterFactory(GsonConverterFactory.create(gson)) for Retrofit 2, or .setConverter(new GsonConverter(gson)) for Retrofit 1 (if I'm not wrong).

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105