简体   繁体   中英

Android Retrofit and GSON: JSON response returns object or string as property value

The JSON data returned from the server can either return an object, or if that object is null, it returns an empty string ("").

My problem is that my DTO expects an object, but it sees a string and crashes.

PersonDTO

data class PersonDto(
    @SerializedName("firstName") val first: String,
    @SerializedName("lastName") val last: String,
    @SerializedName("favorites") val favorites: FavoriteDto,
)

FavoriteDto

class FavoriteDto(
    @SerializedName("color") val color: String,
    @SerializedName("number") val number: Int
)

Different responses from server

"person" : {
    "firstName": "Steve",
    "lastName" : "Johnson",
    "favorites" : {
        "color": "Purple",
        "number": 25
    }
}

...

"person" : {
    "firstName": "Steve",
    "lastName" : "Johnson",
    "favorites" : ""
}

I've heard that I might need a custom GSON deserializer, but I've never done anything with GSON other than the out of the box stuff - so I was hoping for a nudge in the right direction.

Thanks!

Easiest hack is that you can add extra fields in the class with the same serialised name but with a String data type. Like this -

data class PersonDto(
    @SerializedName("firstName") val first: String,
    @SerializedName("lastName") val last: String,
    @SerializedName("favorites") val favorites: FavoriteDto,
    @SerializedName("favorites") val favoritesStr: String,
)

As there is nothing in Gson as "Required" field, you'll just get a null in your deserialized object if something is missing in the JSON. So, if there is an empty string the FavoriteDto object will be null and not null otherwise.

EDIT

I'm adding some Java code that I have written earlier. This might help:

public class PersonDto {
    private FavoriteDto favorites;
    private String favoritesStr;
    public FavoriteDto getResponseDataObject() {
        return favorites;
    }
    public void setResponseDataObject(FavoriteDto favorites) {
        this.favorites = favorites;
    }
    public String getResponseDataString() {
        return favoritesStr;
    }
    public void setResponseDataString(String favoritesStr) {
        this.favoritesStr = favoritesStr;
    }

Defining the Deserializar:

public static class ArrayObjectDualityDeserializer implements JsonDeserializer<PersonDto> {

        public PersonDto deserialize(JsonElement json, Type typeOfT,
                                         JsonDeserializationContext context) throws JsonParseException {
            PersonDto response = new PersonDto();
            JsonObject object = json.getAsJsonObject();
            if(object.get("favorites").isJsonArray()) {

            } else if(object.get("favorites").isJsonObject()) {
                try {
                    FavoriteDto dtoObject = gson.fromJson(object.get("favorites"), FavoriteDto.class);
                    response.setResponseDataObject(dtoObject);
                } catch (JsonSyntaxException e) {
                    DebugLogger.e("Error " + e);
                }
            }  else if (object.get("favorites").isJsonNull()) {

            } else {
                response.setResponseDataString(object.get("favorites").getAsString());
            }
        }
    }

And:

public static Gson gson = new GsonBuilder()
            .registerTypeAdapter(PersonDto.class, new ArrayObjectDualityDeserializer())
            .create();

Lastly:

private static Retrofit retrofit = null;
    private static OkHttpClient.Builder httpClient = null;
    private static OkHttpClient session_client = null;
httpClient = new OkHttpClient.Builder();
            httpClient.addInterceptor(new SessionOkHttpInterceptor());
            session_client = httpClient.build();
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(session_client)
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .build();

The answer of @Subrato M. is partially correct. In order to work, you should remove @SerializedName("favorites") annotation from both fields in order to work. In this case, Gson won't throw an error when deserialize multiple json fields named favorites because you don't have any field annotated with that name (also don't name fileds the same as expected field name beucase Gson will try to deserialize). Use the custom deserializer to check if is an object or a string.

现在不需要@Subrato M 的 hack,你没有得到正确的 json 因为你在class FavoriteDto之前缺少 data 关键字,这就是为什么不存在 get 和 set 方法

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM