Как разобрать ответ от многочастного запроса формы данных в Retrofit2 Android - PullRequest
0 голосов
/ 10 января 2019

Я использую 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 голосов
/ 11 января 2019

Ответ, который я дам вам, будет использовать Gson streaming (streaming for speed) и 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. Таким образом, решение с Retrofit 2 при использовании Multipart должно добавить следующее (поверх запроса):

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