【问题标题】:Format JSON Body for Retrofit from single string value without model从没有模型的单个字符串值格式化 JSON 正文以进行改造
【发布时间】:2016-07-06 12:22:17
【问题描述】:

有没有办法将单个字符串值(纯文本,而不是 json) 转换为带有注释的 JSON 正文?我不想创建这么简单的模型。

示例

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

class BarModel {
    public String bar;
}

会给我我所期望的:

{
    "bar" : "hello world"
}

有没有一种简单的方法来使用注解?像这样的:

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

【问题讨论】:

标签: android json retrofit2


【解决方案1】:

Retrofit 有一个 Converter.Factory 抽象类,您可以使用它来进行自定义 HTTP 表示。如果方法有特定的注解,您可以创建一个 Converter 来构造 okhttp.RequestBody

最终结果将如下所示:

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

并转换:postBar("Hello World") 进入{ "bar" : "Hello World" }

让我们开始吧。

第 1 步 - 为根键 (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();
}

第 2 步 - 定义检测注释的 Converter.Factory (JSONConverterFactory.java)。我使用Gson 进行 JSON 解析,但你可以使用任何你想要的框架。

/**
 * 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));
        }
    }
}

第 3 步 - 将 JSONConverterFactory 安装到您的 Retrofit 实例中

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

第 4 步 - 利润

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

或者对于您的情况:

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

【讨论】:

  • "Core" 改造是一个 http 客户端(而且只有那个 $。我确实问过他们是否在核心库中包含更好的 JSON 支持,但他们并不感兴趣。改造被明确设计为扩展,所以根据设计师的说法,这是“正确”的方法。
  • @Nick,还有什么可以让这个答案变得更好/还有什么你不想接受的理由吗?
  • 我希望有一个单一的解决方案来解决我的问题,不得不扩展这么多是矫枉过正的。但是我会接受它,因为它可能会帮助其他人
  • 一点也不夸张。这是按预期工作的合适的动态解决方案。谢谢凯文!
【解决方案2】:

它不一定创建一个 json 主体,但你的 api 可能能够处理 url 编码的东西

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

【讨论】:

  • 谢谢,没试过。这不是我可以控制的 API,所以期待一个像我发布的那样的 json 主体。 (这给了我 bar=hello%20world)
  • 你仍然可以尝试一下,这个可以经常用来代替body。
【解决方案3】:

那么最好使用Hashmap&lt;String, String&gt;。您可以通过使用 Gson 进行解析,直接在 body 中传递 Hashmap。 如果您需要使用多个地方,那么您可以使用自己的地图扩展 HashMap,这样您就可以在一行中添加您的值。我正在发我的,可能对你有帮助 -

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);
    }
}

用法会像 -

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

希望它会有所帮助:)

【讨论】:

    【解决方案4】:

    我是这样使用的:

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

    它会将 Map 变成 JsonObject

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-20
      • 2012-06-06
      • 1970-01-01
      • 1970-01-01
      • 2021-12-06
      • 1970-01-01
      • 2020-12-30
      • 1970-01-01
      相关资源
      最近更新 更多