2016-07-06 27 views
5

C'è un modo per trasformare un singolo valore di stringa (testo normale, non json) in un corpo JSON con un'annotazione? Non voglio creare un modello così semplice.Formato JSON Body per Retrofit da un valore a stringa singola senza il modello

Esempio

@POST("foo/{fooId}/bars") 
Observable<Void> postBar(@Path("fooId") String styleId, @Body BarModel bar); 

class BarModel { 
    public String bar; 
} 

mi darà quello che mi aspetto:

{ 
    "bar" : "hello world" 
} 

C'è un modo semplice per farlo con un'annotazione? Qualcosa di simile a questo:

@POST("foo/{fooId}/bars") 
Observable<Void> postBar(@Path("fooId") String styleId, @Body("bar") String bar); 
+0

Vedi anche questo [meta-discussione] (http://meta.stackoverflow.com/q/327487/230513). – trashgod

risposta

7

Retrofit ha una classe astratta Converter.Factory che è possibile utilizzare per fare una rappresentazione HTTP personalizzata. È possibile creare un convertitore per costruire un okhttp.RequestBody se il metodo ha un'annotazione specifica.

Il risultato finale sarà simile:

@POST("/") 
Call<Void> postBar(@Body @Root("bar") String foo) 

e trasformare: postBar("Hello World") in { "bar" : "Hello World" }.

Iniziamo.

Fase 1 - creare un'annotazione per la chiave principale (Root.java)

/** 
* Denotes the root key of a JSON request. 
* <p> 
* Simple Example: 
* <pre><code> 
* &#64;POST("/") 
* Call&lt;ResponseBody&gt; example(
*  &#64;Root("name") String yourName); 
* </code></pre> 
* Calling with {@code foo.example("Bob")} yields a request body of 
* <code>{name=>"Bob"}</code>. 
* @see JSONConverterFactory 
*/ 
@Documented 
@Target(PARAMETER) 
@Retention(RUNTIME) 
public @interface Root { 
    /** 
    * The value of the JSON root. 
    * Results in {"value" : object} 
    */ 
    String value(); 
} 

Fase 2 - definire il Converter.Factory che rileva l'annotazione (JSONConverterFactory.java). Sto usando Gson per l'analisi JSON ma puoi usare qualsiasi framework tu voglia.

/** 
* Converts @Root("key") Value to {"key":json value} using the provided Gson converter. 
*/ 
class JSONConverterFactory extends Converter.Factory { 
    private final Gson gson; 
    private static final MediaType CONTENT_TYPE = 
      MediaType.parse("application/json"); 

    JSONConverterFactory(Gson gson) { 
     this.gson = gson; 
    } 

    @Override 
    public Converter<?, RequestBody> requestBodyConverter(
      Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 
     for (Annotation annotation : parameterAnnotations) { 
      if (annotation instanceof Root) { 
       Root rootAnnotation = (Root) annotation; 
       return new JSONRootConverter<>(gson, rootAnnotation.value()); 
      } 
     } 
     return null; 
    } 

    private final class JSONRootConverter<T> implements Converter<T, RequestBody> { 
     private Gson gson; 
     private String rootKey; 

     private JSONRootConverter(Gson gson, String rootKey) { 
      this.gson = gson; 
      this.rootKey = rootKey; 
     } 

     @Override 
     public RequestBody convert(T value) throws IOException { 
      JsonElement element = gson.toJsonTree(value); 
      JsonObject object = new JsonObject(); 
      object.add(this.rootKey, element); 
      return RequestBody.create(CONTENT_TYPE, this.gson.toJson(object)); 
     } 
    } 
} 

Fase 3 - installare il JSONConverterFactory nella tua istanza retrofit

Gson gson = new GsonBuilder().create(); // Or your customized version 
Retrofit.Builder builder = ...; 
builder.addConverterFactory(new JSONConverterFactory(gson)) 

Fase 4 - Utile

@POST("/") 
Call<Void> postBar(@Body @Root("bar") String foo) 

O per il vostro caso:

@POST("foo/{fooId}/bars") 
Observable<Void> postBar(@Body @Root("bar") String barValue, @Path("fooId") String styleId); 
+0

Il retrofit "core" è un client http (e solo $. Ho chiesto loro di includere un migliore supporto JSON nella libreria principale e non erano interessati. Il retrofit è stato appositamente progettato per essere esteso, quindi questo è l'approccio "giusto" – Kevin

+0

@Nick, c'è qualcos'altro che potrebbe rendere questa risposta migliore/qualsiasi ragione per cui non vuoi ancora accettarla? – Kevin

+0

Speravo che ci sarebbe stata una soluzione one-liner per il mio problema di dover estendere questo è eccessivo, ma lo accetterò perché è probabile che aiuti gli altri –

0

Non necessariamente creare un corpo JSON, ma il vostro api potrebbe essere in grado di lavorare con URL codificato cose

@FormUrlEncoded 
@POST("foo/{fooId}/bars") 
Observable<Void> postBar(@Path("fooId") String styleId, @Field("bar") String bar); 
+0

Grazie, non ci avevo provato. Non è un API di cui ho il controllo, quindi mi aspetto un corpo JSON come quello che ho postato. (Questo mi ha dato bar = ciao% 20world) –

+0

puoi ancora provarlo, questo può essere usato al posto del corpo abbastanza spesso. –

2

Quindi meglio usare Hashmap<String, String>. Puoi passare Hashmap direttamente nel corpo, analizzando usando Gson. E se hai bisogno di utilizzare più postazioni, puoi utilizzare la tua mappa estendendo HashMap, in modo da poter aggiungere i tuoi valori in un'unica riga. Vi metto il mio, potrebbe aiuti voi -

public class PostParams extends HashMap<String, String> { 
    public static PostParams init() { 
     return new PostParams(); 
    } 

    public PostParams add(String param, String value) { 
     put(param, value); 
     return this; 
    } 

    public PostParams add(String param, String[] values) { 
     put(param, new Gson().toJson(values)); 
     return this; 
    } 

    public PostParams add(String param, int[] values) { 
     put(param, new Gson().toJson(values)); 
     return this; 
    } 

    public PostParams add(String param, int value) { 
     put(param, value + ""); 
     return this; 
    } 

    public PostParams addPlatform() { 
     put("Platform", Constants.ANDROID); 
     return this; 
    } 

    public PostParams add(String param, double value) { 
     put(param, new Gson().toJson(value)); 
     return this; 
    } 

    @Override 
    public String toString() { 
     return new Gson().toJson(this); 
    } 
} 

Uso sarà come -

String postData = new PostParams().add("bar", "Hello World").toString() 

auguriamo che contribuiscano :)

0

io uso come questo:

@POST("url") 
Observable<Response<Object>> login(@Body Map<String, Object> body); 

Cambierà mappa in JsonObject

Problemi correlati