2017-02-17 1 views
0

У меня есть ответ JSON из API, как это:Как получить внутренний объект JSON с помощью GSON/Retrofit, когда внешнее имя является переменной?

{"asalas77": 
    {"id":23519033,"name":"Asalas77","profileIconId":22,"revisionDate":1487214366000,"summonerLevel":30} 
} 

И мне нужно, чтобы извлечь внутренний объект из него. Я попытался использовать десериализатор, как показано в этом вопросе Get nested JSON object with GSON using retrofit, но он не работает для меня.

public class SummonerDeserializer implements JsonDeserializer<Summoner> { 
@Override 
public Summoner deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) 
     throws JsonParseException { 
    long id = je.getAsJsonObject().get("id").getAsLong(); 
    String name = je.getAsJsonObject().get("name").getAsString(); 
    int profileIconId = je.getAsJsonObject().get("profileIconId").getAsInt(); 
    long revisionDate = je.getAsJsonObject().get("revisionDate").getAsLong(); 
    long summonerLevel = je.getAsJsonObject().get("summonerLevel").getAsLong(); 
    Summoner s = new Summoner(); 
    s.setId(id); 
    s.setName(name); 
    s.setProfileIconId(profileIconId); 
    s.setRevisionDate(revisionDate); 
    s.setSummonerLevel(summonerLevel); 

    return s; 
} 
} 

Но проблема в том, что я не могу получить доступ к внутренним поля из JsonElement je и имя asalas77 является переменной (это поисковый запрос), поэтому я не могу извлечь внутренний объект непосредственно.

ответ

1

У вас должен быть класс-оболочка, чтобы не нарушать стратегии десериализации. Предположим, что это выглядит следующим образом:

final class SummonerResponse { 

    private final Summoner summoner; 

    private SummonerResponse(final Summoner summoner) { 
     this.summoner = summoner; 
    } 

    static SummonerResponse summonerResponse(final Summoner summoner) { 
     return new SummonerResponse(summoner); 
    } 

    Summoner getSummoner() { 
     return summoner; 
    } 

} 

После этого вы можете либо создать десериализатор пользовательский ответ:

final class SummonerWrapperDeserializer 
     implements JsonDeserializer<SummonerResponse> { 

    private static final JsonDeserializer<SummonerResponse> summonerDeserializer = new SummonerWrapperDeserializer(); 

    private SummonerWrapperDeserializer() { 
    } 

    static JsonDeserializer<SummonerResponse> getSummonerResponseDeserializer() { 
     return summonerDeserializer; 
    } 

    @Override 
    public SummonerResponse deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) 
      throws JsonParseException { 
     // Pick the root as a JSON object 
     final JsonObject outerJsonObject = jsonElement.getAsJsonObject(); 
     // And check how many properties does it have 
     final Iterable<? extends Entry<String, JsonElement>> outerJsonObjectEntries = outerJsonObject.entrySet(); 
     if (outerJsonObject.size() != 1) { 
      throw new JsonParseException("Expected one property object, the actual properties are: " + getPropertyName(outerJsonObjectEntries)); 
     } 
     // If it has only one property, just get the property and take its inner value 
     final JsonElement innerJsonElement = outerJsonObjectEntries.iterator().next().getValue(); 
     // Once it's obtained, just delegate the parsing to a downstream parser - no need to create Summoner instances by hands 
     return summonerResponse(context.deserialize(innerJsonElement, Summoner.class)); 
    } 

    private static Set<String> getPropertyName(final Iterable<? extends Entry<String, JsonElement>> entries) { 
     final Set<String> keys = new LinkedHashSet<>(); 
     for (final Entry<String, JsonElement> entry : entries) { 
      keys.add(entry.getKey()); 
     } 
     return keys; 
    } 

} 

Или сохранить некоторую память (в JSON (де) сериализаторов требуют некоторой памяти, так как они работают с JSON деревьев) и создать адаптер более низкий уровень типа:

final class SummonerResponseTypeAdapterFactory 
     implements TypeAdapterFactory { 

    private static final TypeAdapterFactory summonerResponseTypeAdapterFactory = new SummonerResponseTypeAdapterFactory(); 

    private SummonerResponseTypeAdapterFactory() { 
    } 

    static TypeAdapterFactory getSummonerResponseTypeAdapterFactory() { 
     return summonerResponseTypeAdapterFactory; 
    } 

    @Override 
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) { 
     // Check if we can handle SummonerResponse. Classes can be compared with `==` 
     if (typeToken.getRawType() == SummonerResponse.class) { 
      final TypeAdapter<SummonerResponse> typeAdapter = getSummonerResponseTypeAdapter(gson); 
      @SuppressWarnings("unchecked") 
      final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter; 
      return castTypeAdapter; 
     } 
     return null; 
    } 

} 
final class SummonerResponseTypeAdapter 
     extends TypeAdapter<SummonerResponse> { 

    private final Gson gson; 

    private SummonerResponseTypeAdapter(final Gson gson) { 
     this.gson = gson; 
    } 

    static TypeAdapter<SummonerResponse> getSummonerResponseTypeAdapter(final Gson gson) { 
     return new SummonerResponseTypeAdapter(gson); 
    } 

    @Override 
    @SuppressWarnings("resource") 
    public void write(final JsonWriter out, final SummonerResponse summonerResponse) 
      throws IOException { 
     // The incoming object may be null 
     if (summonerResponse == null && gson.serializeNulls()) { 
      out.nullValue(); 
      return; 
     } 
     // Generate the inner object 
     out.beginObject(); 
     out.name(summonerResponse.getSummoner().name); 
     gson.toJson(summonerResponse.getSummoner(), Summoner.class, out); 
     out.endObject(); 
    } 

    @Override 
    public SummonerResponse read(final JsonReader in) 
      throws IOException { 
     // is it a null? 
     if (in.peek() == NULL) { 
      return null; 
     } 
     // make sure that the inner read JSON contains an inner object 
     in.beginObject(); 
     // ignore the name 
     in.nextName(); 
     // delegate parsing to the backing Gson instance in order to apply downstream parsing 
     final Summoner summoner = gson.fromJson(in, Summoner.class); 
     // check if there are more properties within the inner object 
     if (in.peek() == NAME) { 
      throw new MalformedJsonException("Unexpected: " + in.nextName()); 
     } 
     // consume the "}" token 
     in.endObject(); 
     return summonerResponse(summoner); 
    } 

} 

Тогда любой из приведенных выше вариантов может быть U СЭД, как это:

final Gson gson = new GsonBuilder() 
     .registerTypeAdapter(SummonerResponse.class, getSummonerResponseDeserializer()) 
     .create(); 
final SummonerResponse summonerResponse = gson.fromJson(JSON, SummonerResponse.class); 
final Summoner summoner = summonerResponse.getSummoner(); 
out.println(summoner.id + " => " + summoner.name); 

или

final Gson gson = new GsonBuilder() 
     .registerTypeAdapterFactory(getSummonerResponseTypeAdapterFactory()) 
     .create(); 
final SummonerResponse summonerResponse = gson.fromJson(JSON, SummonerResponse.class); 
final Summoner summoner = summonerResponse.getSummoner(); 
out.println(summoner.id + " => " + summoner.name); 
out.println(gson.toJson(summonerResponse)); 

Выходы

23519033 => Asalas77

и

23519033 => Asalas77
{ "Asalas77": { "идентификатор": 23519033, "название": "Asalas77", "profileIconId": 22, "revisionDate": 1487214366000, "summonerLevel": 30}}

соответственно.

Смежные вопросы