2017-01-18 2 views
1

Общий шаблон, который я вижу при написании заглушек для REST-интерфейсов с Retrofit2, заключается в том, что часто этот параметр необходимо обернуть картой с одной записью (или, что еще хуже, классом-оболочкой, состоящим из класса с одним полем).Возможно избежать карт с одиночной записью JSON-wrapper?

Типичные полезные данные JSON выглядят как {"idontcareaboutthis": { // Data I actually want.... Есть ли способ очистить эту относительно бесполезную внешнюю оболочку? Мне кажется странным, что все мои методы REST имеют возвращаемый тип Карты.

+0

Использование преобразователей. 1. http://square.github.io/retrofit/#restadapter-configuration 2. «Retrofit.Builder.addConverterFactory (фабрика Converter.Factory)' 3. (При использовании Gson) Создайте файл, похожий на GsonConverterFactory. java добавляет вашу пользовательскую логику. – Sangharsh

ответ

1

Вам не нужна карта. Вы можете написать свой собственный десериализатор JSON. Скажем, у вас есть следующие JSON, где вам не нужно корневой объект один ключ:

{ 
    "idontcareaboutthis": { 
     "foo": 1, 
     "bar": 2 
    } 
} 

Тогда JSON десериализатор может выглядеть следующим образом:

final class ResponseJsonDeserializer<T> 
     implements JsonDeserializer<T> { 

    private final Gson backingGson; 

    private ResponseJsonDeserializer(final Gson backingGson) { 
     this.backingGson = backingGson; 
    } 

    static <T> JsonDeserializer<T> getResponseJsonDeserializer(final Gson backingGson) { 
     return new ResponseJsonDeserializer<>(backingGson); 
    } 

    @Override 
    public T deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context) 
      throws JsonParseException { 
     final JsonObject root = json.getAsJsonObject(); 
     final Set<Entry<String, JsonElement>> entries = root.entrySet(); 
     final int propertyCount = entries.size(); 
     if (propertyCount != 1) { 
      throw new JsonParseException("Expected a single property root object, but got an object with " + propertyCount + " properties"); 
     } 
     final Entry<String, JsonElement> inner = entries.iterator().next(); 
     // Can't use context.deserialize here due to recursion 
     return backingGson.fromJson(inner.getValue(), type); 
    } 

} 

Обратите внимание, как выше десериализатор извлекает root и как он делегирует процесс десериализации на другой экземпляр Gson. Теперь вам нужно создать экземпляр Gson, который знает о свойстве idontcareaboutthis.

private static final Gson registryBackingGson = new GsonBuilder() 
      // whatever necessary here 
      .create(); 

    private static final Gson registryGson = new GsonBuilder() 
      .registerTypeAdapter(FooBarResponse.class, getResponseJsonDeserializer(registryBackingGson)) 
      // add another response classes here like the above, but do not register other types - they must be registered in registryBackingGson 
      .create(); 

registryGson требует либо перечисления всех классов ответа или регистрацию определенной иерархии типов. Если первый случай вам не очень удобен, и вы можете изменить исходный код классов ответов, вы можете добавить специальный интерфейс маркера, чтобы зарегистрировать всю иерархию типов. Скажем, что-то вроде этого:

private static final Gson registryBackingGson = new GsonBuilder() 
      // whatever necessary here 
      .create(); 

    private static final Gson registryGson = new GsonBuilder() 
      .registerTypeHierarchyAdapter(IResponse.class, getResponseJsonDeserializer(registryBackingGson)) 
      // no need to add another "response" classes here - they just must implement the marker interface 
      .create(); 

Объект передачи данных:

final class FooBarResponse { 

    // The `final` modifier is a reasonable habit for incoming DTO classes, but primitive constants are inlined by the compiler. 
    // Suppressing the inlining can be done be a simple workaround to make javac think that it's not a real constant. 
    // However, it's a matter of your code style, and this is just an example. 
    private final int foo = constOf(0); 
    private final int bar = constOf(0); 

    int getFoo() { 
     return foo; 
    } 

    int getBar() { 
     return bar; 
    } 

    // We're cheating... 
    private static int constOf(final int i) { 
     return i; 
    } 

} 

А если вы предпочитаете интерфейс маркеров и регистрацию иерархии

interface IResponse { 
} 

final class FooBarResponse 
     implements IResponse { 
... 

всего типа А как это работает :

final FooBarResponse fooBar = registryGson.fromJson(JSON, FooBarResponse.class) 
out.println(fooBar.getFoo()); // 1 
out.println(fooBar.getBar()); // 2 

дооснащения адаптер:

final Retrofit retrofit = new Retrofit.Builder() 
     // ... 
     .addConverterFactory(GsonConverterFactory.create(registryGson)) 
     .build(); 

Таким образом, ваши методы дооснащения-базовый интерфейс может вернуть FooBar/и т.д. экземпляров класса, а не карты.