如何在Retrofit2 Android中解析来自多部分表单数据请求的响应

时间:2019-01-10 17:02:59

标签: android retrofit2 multipartform-data

我正在使用Retrofit 2创建一个多部分表单数据请求,该请求可以正常运行,并且服务器响应200。我在解析响应时遇到问题。这是我的代码:

@POST("sync/mediaUpload")
@Multipart
Call<ResponseBody> uploadMediaFile(@Header("Authorization") String token,
                                          @Part("userId") RequestBody userId,
                                          @Part MultipartBody.Part file,
                                          @Part("fileId") RequestBody photoId,
                                          @Part("hash") RequestBody hash);


public Response<ResponseBody> uploadMediaFile(String token, String userId, File file, String fileName, String fileId, String hash) {

    MediaService service = retrofit.create(MediaService.class);
    MultipartBody.Part fileBody = prepareFilePart("file", file);
    RequestBody userIdBody = RequestBody.create(MediaType.parse("text/plain"), userId);
    RequestBody fileNameBody = RequestBody.create(MediaType.parse("text/plain"), fileName);
    RequestBody fileIdBody = RequestBody.create(MediaType.parse("text/plain"), fileId);
    RequestBody hashBody = RequestBody.create(MediaType.parse("text/plain"), hash);
    Call<ResponseBody> call = service.uploadMediaFile(token, userIdBody, txIdBody, transIdBody, stepCodeBody,
            fileBody, fileNameBody, fileIdBody, hashBody);
    try {
        return call.execute();
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

 @NonNull
private MultipartBody.Part prepareFilePart(String partName, File file) {
    RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), file);
    return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
}

正确上传文件后,服​​务器将返回Json对象。示例:

{
    "fileName": "IMG_20190108_183751.jpg",
    "fileId": "0",
    "fileSizeInBytes": 216067
}

但是,在call.execute()中,改造会返回:

--MultipartDataMediaFormatterBoundary1q2w3e
Content-Disposition: form-data; name="FileName"
IMG_20190108_183751.jpg

--MultipartDataMediaFormatterBoundary1q2w3e
Content-Disposition: form-data; name="FileId"
0

--MultipartDataMediaFormatterBoundary1q2w3e
Content-Disposition: form-data; name="FileSizeInBytes"
216067

如何解析该回复? 我试图通过使用对象而不是ResponseBody来更改改装服务的签名:

@POST("sync/mediaUpload")
@Multipart
Call<MediaUploadResponse> uploadMediaFile(@Header("Authorization") String token,
                                          @Part("userId") RequestBody userId,
                                          @Part MultipartBody.Part file,
                                          @Part("fileId") RequestBody photoId,
                                          @Part("hash") RequestBody hash);

还有我的对象

public class MediaUploadResponse {

public final String fileName;
public final String fileId;
public final long fileSizeInBytes;

    public MediaUploadResponse(String fileName, String fileId, long 
    fileSizeInBytes) {
        this.fileName = fileName;
        this.fileId = fileId;
        this.fileSizeInBytes = fileSizeInBytes;
    }
}

但是翻新会引发MalformedJsonException

有人知道如何解决吗?

谢谢。

1 个答案:

答案 0 :(得分:0)

我将为您提供的答案将使用Gson streaming(以流的形式传输)和okhttp3。请记住,此代码当前未显示,未经测试。它是向您展示做什么的想法。我从我当前运行的应用程序之一中获得了该想法(该想法已实现并正在运行)。它可能看起来像是一个过大的杀伤力。由于模糊点,您还有另一个问题,请在下面留下评论。

1-进行GSON改装:

package whatever.package.you.want;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

public class DataService {
    private static Retrofit retrofit = null;
    private static final int CONNECTION_TIMEOUT = 45;//s
    private static final int READ_TIMEOUT = 45;//s
    private static final int WRITE_TIMEOUT = 45;//s
    private static final String MEDIA_TYPE = "application/json";//"multipart/form-data"; //"text/plain";
    private static final DATA_SERVICE_BASE_URL = "https://stackoverflow.com"; // your desired URL
    //I suppose you have your custom declarations here

    public static Retrofit getClient(String yourURL) {

        Gson gson = new GsonBuilder()
                .setLenient()
                .setPrettyPrinting()
                .create();

        //https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
        /*HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);*/

        final OkHttpClient client = new OkHttpClient.Builder()
                /*.addInterceptor(logging)*/
                .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)
                .build();

        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(yourURL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create(gson)) //https://github.com/square/retrofit/tree/master/retrofit-converters/gson
                    .addConverterFactory(ScalarsConverterFactory.create()) //https://github.com/square/retrofit/tree/master/retrofit-converters/scalars
                    .build();
        }
        return retrofit;
    }

    public static DataService getUserDataService() {
        return getClient(DATA_SERVICE_BASE_URL).create(UserDataServiceInterface.class);
    }
}

2-您的模型MediaUploadResponse.class

package whatever.package.you.want;

import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;


@JsonAdapter(MediaUploadResponseAdapter.class)
public class MediaUploadResponse {

    @SerializedName("fileName")
    private String fileName = "";

    @SerializedName("fileId")
    private String fileID = "";

    @SerializedName("fileSizeInBytes")
    private long fileSizeInBytes = "";


    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFileId() {
        return fileId;
    }

    public void setFileId(String fileId) {
        this.fileId = fileId;
    }

    public String getFileSizeInBytes() {
        return fileSizeInBytes;
    }

    public void setFileSizeInBytes(long fileSizeInBytes) {
        this.fileSizeInBytes = fileSizeInBytes;
    }
}

3-模型的适配器MediaUploadResponseAdapter.class,用于序列化和反序列化:

package whatever.package.you.want;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import whatever.MediaUploadResponse;
import whatever.JsonAdapterUtils;

import java.io.IOException;


public class MediaUploadResponseAdapter extends BaseJsonAdapter<MediaUploadResponse> {

    @Override
    public MediaUploadResponse read(JsonReader reader) throws IOException {
        MediaUploadResponse element = new MediaUploadResponse();
        String fieldName = null;

        if(reader.peek() == JsonToken.NULL){
            reader.nextNull();
            return null;
        }

        reader.beginObject();
        while (reader.hasNext()) {
            JsonToken token = reader.peek();
            if(token.equals(JsonToken.NAME))
                fieldName = reader.nextName();

            if (fieldName.equals("fileName") && token != JsonToken.NULL)
                element.setFileName(JsonAdapterUtils.stringFromJsonReader(reader));

            else if (fieldName.equals("fileId") && token != JsonToken.NULL)
                element.setFileID(JsonAdapterUtils.stringFromJsonReader(reader));

            else if (fieldName.equals("fileSizeInBytes") && token != JsonToken.NULL)
                element.SetFileSizeInBytes(JsonAdapterUtils.longFromJsonReader(reader));

            else
                reader.skipValue();
        }
        reader.endObject();

        return element;
    }

    @Override
    public void write(JsonWriter writer, MediaUploadResponse element) throws IOException {
        if(element == null){
            writer.nullValue();
            return;
        }
        writer.beginObject();
        writer.name("fileName").value(element.getFileName());
        writer.name("fileId").value(element.getFileId());
        writer.name("fileSizeInBytes").value(element.getFileSizeInBytes());
        writer.endObject();
    }
}

4-使用此呼叫(您发布的第二个呼叫)(编辑:这是主要问题的答案):

@Headers({
        "Accept: application/json"
})
@POST("sync/mediaUpload")
@Multipart
Call<MediaUploadResponse> uploadMediaFile(@Header("Authorization") String token,
                                          @Part("userId") RequestBody userId,
                                          @Part MultipartBody.Part file,
                                          @Part("fileId") RequestBody photoId,
                                          @Part("hash") RequestBody hash);

5-有一些好处,这样您就不会错过某些依赖项:

a- BaseJsonAdapter.class类(将有助于解析列表):

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by mamboa on 3/6/2018.
 */

public class BaseJsonAdapter<T> extends TypeAdapter<T>{

    public ArrayList<T> readArray(JsonReader reader) throws IOException {
        if(reader.peek() == JsonToken.NULL){
            reader.nextNull();
            return null;
        }

        ArrayList<T> elements = new ArrayList<T>();

        reader.beginArray();
        while (reader.hasNext()) {
            T value = read(reader);
            if(value != null)
                elements.add(value);
            else {
             break;
            }
        }
        reader.endArray();
        return elements;
    }

    public void writeArray(JsonWriter writer, List<T> messages) throws IOException {
        writer.beginArray();
        for (T message : messages) {
            write(writer, message);
        }
        writer.endArray();
    }

    public T read(JsonReader reader) throws IOException {
        return null;
    }

    public void write(JsonWriter writer, T t) throws IOException {
    }
}

b-最后是JsonAdapterUtils

package whatever.Utils;


import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;

import java.io.IOException;

/**
 * Created by mamboa on 3/7/2018.
 */

public class JsonAdapterUtils {

    public static final int INTEGER_DEFAULT = -1;
    public static final String STRING_DEFAULT = "";
    public static final boolean BOOLEAN_DEFAULT = false;

    public static int intFromJsonReader(JsonReader reader) throws IOException{
        try {
            if(reader.peek() == JsonToken.BOOLEAN)
                return  fromBooleanToInt(reader.nextBoolean());

            String resultValue = reader.nextString();
            if("".equals(resultValue))
                return INTEGER_DEFAULT;

            return Integer.parseInt(resultValue);
        }
        catch (IOException ex){
            return returnDefaultAfterException(reader);
        }
        catch (IllegalStateException ex){
            return returnDefaultAfterException(reader);
        }
        catch (NumberFormatException ex){
            return returnDefaultAfterException(reader);
        }
    }

    public static long longFromJsonReader(JsonReader reader) throws IOException{
        try {
            if(reader.peek() == JsonToken.BOOLEAN)
                return  fromBooleanToInt(reader.nextBoolean());

            String resultValue = reader.nextString();
            if("".equals(resultValue))
                return INTEGER_DEFAULT;

            return Long.parseLong(resultValue);
        }
        catch (IOException ex){
            return returnDefaultAfterException(reader);
        }
        catch (IllegalStateException ex){
            return returnDefaultAfterException(reader);
        }
        catch (NumberFormatException ex){
            return returnDefaultAfterException(reader);
        }
    }

    public static float floatFromJsonReader(JsonReader reader) throws IOException{
        try {
            if(reader.peek() == JsonToken.BOOLEAN)
                return  fromBooleanToInt(reader.nextBoolean());

            String resultValue = reader.nextString();
            if("".equals(resultValue))
                return INTEGER_DEFAULT;

            return Float.parseFloat(resultValue);
        }
        catch (IOException ex){
            return returnDefaultAfterException(reader);
        }
        catch (IllegalStateException ex){
            return returnDefaultAfterException(reader);
        }
        catch (NumberFormatException ex){
            return returnDefaultAfterException(reader);
        }
    }

    public static double doubleFromJsonReader(JsonReader reader) throws IOException{
        try {
            if(reader.peek() == JsonToken.BOOLEAN)
                return  fromBooleanToInt(reader.nextBoolean());

            String resultValue = reader.nextString();
            if("".equals(resultValue))
                return INTEGER_DEFAULT;

            return Double.parseDouble(resultValue);
        }
        catch (IOException ex){
            return returnDefaultAfterException(reader);
        }
        catch (IllegalStateException ex){
            return returnDefaultAfterException(reader);
        }
        catch (NumberFormatException ex){
            return returnDefaultAfterException(reader);
        }
    }

    public static String stringFromJsonReader(JsonReader reader) throws IOException{
        String resultValue = "";
        try {
            if(reader.peek() == JsonToken.BOOLEAN)
                return  boolFromJsonReader(reader)? "true" : "false";

            resultValue = reader.nextString();
            return  !resultValue.equals("") ? resultValue : STRING_DEFAULT;
        }
        catch (IOException ex){
            reader.skipValue();
            return STRING_DEFAULT;
        }
        catch (IllegalStateException ex){
            reader.skipValue();
            return STRING_DEFAULT;
        }
    }

    public static boolean boolFromJsonReader(JsonReader reader)throws IOException{
        try {
            if(reader.peek() == JsonToken.BOOLEAN)
                return  reader.peek() == JsonToken.BOOLEAN ? reader.nextBoolean() : BOOLEAN_DEFAULT;
        }
        catch (IOException ex){
            reader.skipValue();
        }
        return BOOLEAN_DEFAULT;
    }

    private static int returnDefaultAfterException(JsonReader reader) throws IOException {
        if(reader != null) reader.skipValue();
        return INTEGER_DEFAULT;
    }

    private static int fromBooleanToInt(boolean value){
        return value ? 1 : 0;
    }

    public static String serializeObject(Object object){
        if(object != null) {
            Gson gson = new Gson();
            return gson.toJson(object);
        }
        return "";
    }
}

编辑:问题:

问题在于,在http请求的参数中,服务器必须知道调用者想要JSON格式的响应。 因此,使用Multipart时使用Retrofit 2解决方案是在请求的顶部添加以下内容:

@Headers({
        "Accept: application/json"
})