Skip to content

Commit 0d1b97a

Browse files
authored
Merge pull request #14813 from woocommerce/issue/WOOMOB-1560-update-json-object-or-empty-array
Fix unexpected API response error for media upload
2 parents 09fa497 + 35123ad commit 0d1b97a

File tree

9 files changed

+108
-44
lines changed

9 files changed

+108
-44
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [Internal] Remove unused dependencies and optimize dependency scopes [https://github.com/woocommerce/woocommerce-android/pull/14710]
1212
- [*] Fix a rare crash in order refund flow [https://github.com/woocommerce/woocommerce-android/pull/14742]
1313
- [*] Fix customer name display when filtering orders from order details [https://github.com/woocommerce/woocommerce-android/pull/14761]
14+
- [*] Fixed a rare media upload error [https://github.com/woocommerce/woocommerce-android/pull/14813]
1415

1516
23.4
1617
-----

libs/fluxc/src/main/java/org/wordpress/android/fluxc/module/ReleaseNetworkModule.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
import org.wordpress.android.fluxc.network.OkHttpStack;
1414
import org.wordpress.android.fluxc.network.OpenJdkCookieManager;
1515
import org.wordpress.android.fluxc.network.RetryOnRedirectBasicNetwork;
16-
import org.wordpress.android.fluxc.network.rest.JsonObjectOrEmptyArray;
17-
import org.wordpress.android.fluxc.network.rest.JsonObjectOrEmptyArrayDeserializer;
16+
import org.wordpress.android.fluxc.network.rest.JsonObjectOrNullAdapterFactory;
1817
import org.wordpress.android.fluxc.network.rest.JsonObjectOrFalse;
1918
import org.wordpress.android.fluxc.network.rest.JsonObjectOrFalseDeserializer;
2019

@@ -132,8 +131,7 @@ public Gson provideGson() {
132131
GsonBuilder gsonBuilder = new GsonBuilder();
133132
gsonBuilder.setLenient();
134133
gsonBuilder.registerTypeHierarchyAdapter(JsonObjectOrFalse.class, new JsonObjectOrFalseDeserializer());
135-
gsonBuilder.registerTypeHierarchyAdapter(JsonObjectOrEmptyArray.class,
136-
new JsonObjectOrEmptyArrayDeserializer());
134+
gsonBuilder.registerTypeAdapterFactory(new JsonObjectOrNullAdapterFactory());
137135
return gsonBuilder.create();
138136
}
139137
}

libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/discovery/RootWPAPIRestResponse.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.wordpress.android.fluxc.network.discovery
22

33
import com.google.gson.annotations.SerializedName
44
import org.wordpress.android.fluxc.network.Response
5-
import org.wordpress.android.fluxc.network.rest.JsonObjectOrEmptyArray
5+
import org.wordpress.android.fluxc.network.rest.JsonObjectOrNull
66

77
class RootWPAPIRestResponse(
88
val name: String? = null,
@@ -14,7 +14,7 @@ class RootWPAPIRestResponse(
1414
) : Response {
1515
class Authentication(
1616
@SerializedName("application-passwords") val applicationPasswords: ApplicationPasswords? = null
17-
) : JsonObjectOrEmptyArray() {
17+
) : JsonObjectOrNull() {
1818
class ApplicationPasswords(
1919
val endpoints: Endpoints?
2020
) {

libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/GsonRequest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ public static GsonBuilder getDefaultGsonBuilder() {
176176
GsonBuilder gsonBuilder = new GsonBuilder();
177177
gsonBuilder.setStrictness(Strictness.LENIENT);
178178
gsonBuilder.registerTypeHierarchyAdapter(JsonObjectOrFalse.class, new JsonObjectOrFalseDeserializer());
179-
gsonBuilder.registerTypeHierarchyAdapter(JsonObjectOrEmptyArray.class,
180-
new JsonObjectOrEmptyArrayDeserializer());
179+
gsonBuilder.registerTypeAdapterFactory(new JsonObjectOrNullAdapterFactory());
181180
gsonBuilder.registerTypeHierarchyAdapter(List.class, new ListOrObjectDeserializer());
182181
gsonBuilder.registerTypeHierarchyAdapter(Object[].class, new ArrayOrObjectDeserializer());
183182
return gsonBuilder;

libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/JsonObjectOrEmptyArrayDeserializer.java

Lines changed: 0 additions & 26 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package org.wordpress.android.fluxc.network.rest;
22

3-
public abstract class JsonObjectOrEmptyArray {}
3+
public abstract class JsonObjectOrNull {}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.wordpress.android.fluxc.network.rest
2+
3+
import com.google.gson.Gson
4+
import com.google.gson.TypeAdapter
5+
import com.google.gson.TypeAdapterFactory
6+
import com.google.gson.reflect.TypeToken
7+
import com.google.gson.stream.JsonReader
8+
import com.google.gson.stream.JsonToken
9+
import com.google.gson.stream.JsonWriter
10+
11+
/**
12+
* Parses fields that should be a JSON object but might come as an empty array (`[]`), null, or a primitive from the
13+
* API. This factory ensures that if the incoming JSON token for a field is not `BEGIN_OBJECT`, it's parsed as `null`
14+
* instead of causing a crash.
15+
*/
16+
class JsonObjectOrNullAdapterFactory : TypeAdapterFactory {
17+
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
18+
if (!JsonObjectOrNull::class.java.isAssignableFrom(type.rawType)) return null
19+
20+
val delegate: TypeAdapter<T> = gson.getDelegateAdapter(this, type)
21+
return object : TypeAdapter<T>() {
22+
override fun write(out: JsonWriter, value: T?) {
23+
delegate.write(out, value)
24+
}
25+
26+
override fun read(`in`: JsonReader): T? = if (`in`.peek() == JsonToken.BEGIN_OBJECT) {
27+
delegate.read(`in`)
28+
} else {
29+
// BEGIN_ARRAY, NULL, primitive vs.
30+
`in`.skipValue()
31+
null
32+
}
33+
}.nullSafe()
34+
}
35+
}

libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/media/MediaWPRESTResponse.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
66
import org.apache.commons.text.StringEscapeUtils
77
import org.wordpress.android.fluxc.model.MediaModel
88
import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState
9-
import org.wordpress.android.fluxc.network.rest.JsonObjectOrEmptyArray
9+
import org.wordpress.android.fluxc.network.rest.JsonObjectOrNull
1010
import org.wordpress.android.fluxc.network.rest.wpcom.media.MediaWPComRestResponse
1111
import org.wordpress.android.util.DateTimeUtils
1212
import java.text.SimpleDateFormat
@@ -28,7 +28,7 @@ data class MediaWPRESTResponse(
2828
@SerializedName("alt_text") val altText: String,
2929
@SerializedName("media_type") val mediaType: String,
3030
@SerializedName("mime_type") val mimeType: String,
31-
@SerializedName("media_details") val mediaDetails: MediaDetails,
31+
@SerializedName("media_details") val mediaDetails: MediaDetails?,
3232
@SerializedName("source_url") val sourceURL: String?
3333
) {
3434
data class Attribute(
@@ -40,13 +40,13 @@ data class MediaWPRESTResponse(
4040
val height: Int,
4141
val file: String?,
4242
val sizes: Sizes?
43-
) : JsonObjectOrEmptyArray()
43+
) : JsonObjectOrNull()
4444

4545
data class Sizes(
4646
val medium: ImageSize?,
4747
val thumbnail: ImageSize?,
4848
val full: ImageSize?
49-
) : JsonObjectOrEmptyArray()
49+
) : JsonObjectOrNull()
5050

5151
data class ImageSize(
5252
val path: String?,
@@ -70,16 +70,16 @@ fun MediaWPRESTResponse.toMediaModel(localSiteId: Int) = MediaModel(
7070
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT).parse(dateGmt)
7171
),
7272
sourceURL.orEmpty(),
73-
mediaDetails.sizes?.thumbnail?.sourceURL,
74-
mediaDetails.file,
75-
mediaDetails.file?.substringAfterLast('.', ""),
73+
mediaDetails?.sizes?.thumbnail?.sourceURL,
74+
mediaDetails?.file,
75+
mediaDetails?.file?.substringAfterLast('.', ""),
7676
mimeType,
7777
StringEscapeUtils.unescapeHtml4(title.rendered),
7878
StringEscapeUtils.unescapeHtml4(caption.rendered),
7979
StringEscapeUtils.unescapeHtml4(description.rendered),
8080
StringEscapeUtils.unescapeHtml4(altText),
81-
mediaDetails.width,
82-
mediaDetails.height,
81+
mediaDetails?.width ?: 0,
82+
mediaDetails?.height ?: 0,
8383
0,
8484
null,
8585
false,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.wordpress.android.fluxc.network.rest
2+
3+
import com.google.gson.Gson
4+
import com.google.gson.GsonBuilder
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertNull
7+
import org.junit.Test
8+
9+
class JsonObjectOrNullAdapterFactoryTest {
10+
data class SampleModel(val id: Int, val name: String?) : JsonObjectOrNull()
11+
12+
private fun gson(): Gson = GsonBuilder()
13+
.registerTypeAdapterFactory(JsonObjectOrNullAdapterFactory())
14+
.create()
15+
16+
@Test
17+
fun `when json is a valid object, then it is deserialized`() {
18+
val json = """{"id": 7, "name": "woo"}"""
19+
val result: SampleModel = gson().fromJson(json, SampleModel::class.java)
20+
assertEquals(SampleModel(7, "woo"), result)
21+
}
22+
23+
@Test
24+
fun `when json is an empty array, then returns null`() {
25+
val json = "[]"
26+
val result: SampleModel? = gson().fromJson(json, SampleModel::class.java)
27+
assertNull(result)
28+
}
29+
30+
@Test
31+
fun `when json is null, then returns null`() {
32+
val json = "null"
33+
val result: SampleModel? = gson().fromJson(json, SampleModel::class.java)
34+
assertNull(result)
35+
}
36+
37+
@Test
38+
fun `when json is a primitive string, then returns null`() {
39+
val json = "\"primitive\""
40+
val result: SampleModel? = gson().fromJson(json, SampleModel::class.java)
41+
assertNull(result)
42+
}
43+
44+
@Test
45+
fun `when json is a primitive number, then returns null`() {
46+
val json = "123"
47+
val result: SampleModel? = gson().fromJson(json, SampleModel::class.java)
48+
assertNull(result)
49+
}
50+
51+
@Test
52+
fun `when json is a primitive boolean, then returns null`() {
53+
val json = "true"
54+
val result: SampleModel? = gson().fromJson(json, SampleModel::class.java)
55+
assertNull(result)
56+
}
57+
}

0 commit comments

Comments
 (0)