Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit 06355b8

Browse files
Merge pull request #3070 from wordpress-mobile/woo/meta-data-refactor-1
[Woo] Metadata refactoring 1
2 parents bdf38f8 + 36dddf6 commit 06355b8

File tree

20 files changed

+411
-319
lines changed

20 files changed

+411
-319
lines changed

example/src/test/java/org/wordpress/android/fluxc/model/WCProductModelTest.kt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.wordpress.android.fluxc.model
22

3-
import com.google.gson.Gson
43
import org.assertj.core.api.Assertions.assertThat
54
import org.junit.Test
65
import org.wordpress.android.fluxc.JsonLoaderUtils.jsonFileAs
@@ -85,12 +84,11 @@ class WCProductModelTest {
8584
?.asProductModel()
8685

8786
assertThat(product?.metadata).isNotNull
88-
val map = Gson()
89-
.fromJson(product?.metadata, Array<WCMetaData>::class.java).associateBy { it.key }
87+
val map = product?.parsedMetaData?.associateBy { it.key }!!
9088

9189
assertThat(map).containsKey(BundleMetadataKeys.BUNDLE_MAX_SIZE)
9290
assertThat(map).doesNotContainKey(BundleMetadataKeys.BUNDLE_MIN_SIZE)
93-
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MAX_SIZE).value).isEqualTo("5")
91+
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MAX_SIZE).valueAsString).isEqualTo("5")
9492
}
9593
@Test
9694
fun `Bundled product with min size is serialized correctly`() {
@@ -99,12 +97,11 @@ class WCProductModelTest {
9997
?.asProductModel()
10098

10199
assertThat(product?.metadata).isNotNull
102-
val map = Gson()
103-
.fromJson(product?.metadata, Array<WCMetaData>::class.java).associateBy { it.key }
100+
val map = product?.parsedMetaData?.associateBy { it.key }!!
104101

105102
assertThat(map).containsKey(BundleMetadataKeys.BUNDLE_MIN_SIZE)
106103
assertThat(map).doesNotContainKey(BundleMetadataKeys.BUNDLE_MAX_SIZE)
107-
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MIN_SIZE).value).isEqualTo("5")
104+
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MIN_SIZE).valueAsString).isEqualTo("5")
108105
}
109106

110107
@Test
@@ -114,13 +111,12 @@ class WCProductModelTest {
114111
?.asProductModel()
115112

116113
assertThat(product?.metadata).isNotNull
117-
val map = Gson()
118-
.fromJson(product?.metadata, Array<WCMetaData>::class.java).associateBy { it.key }
114+
val map = product?.parsedMetaData?.associateBy { it.key }!!
119115

120116
assertThat(map).containsKey(BundleMetadataKeys.BUNDLE_MIN_SIZE)
121117
assertThat(map).containsKey(BundleMetadataKeys.BUNDLE_MAX_SIZE)
122-
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MAX_SIZE).value).isEqualTo("5")
123-
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MIN_SIZE).value).isEqualTo("5")
118+
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MAX_SIZE).valueAsString).isEqualTo("5")
119+
assertThat(map.getValue(BundleMetadataKeys.BUNDLE_MIN_SIZE).valueAsString).isEqualTo("5")
124120
}
125121

126122
@Test
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.wordpress.android.fluxc.model
22

3-
import org.wordpress.android.fluxc.persistence.entity.OrderMetaDataEntity
4-
53
data class OrderAttributionInfo(
64
val sourceType: String? = null,
75
val campaign: String? = null,
@@ -10,14 +8,14 @@ data class OrderAttributionInfo(
108
val deviceType: String? = null,
119
val sessionPageViews: String? = null
1210
) {
13-
constructor(metadata: List<OrderMetaDataEntity>) : this(
14-
sourceType = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.SOURCE_TYPE }?.value,
15-
campaign = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.CAMPAIGN }?.value,
16-
source = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.SOURCE }?.value,
17-
medium = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.MEDIUM }?.value,
18-
deviceType = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.DEVICE_TYPE }?.value,
11+
constructor(metadata: List<WCMetaData>) : this(
12+
sourceType = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.SOURCE_TYPE }?.valueAsString,
13+
campaign = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.CAMPAIGN }?.valueAsString,
14+
source = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.SOURCE }?.valueAsString,
15+
medium = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.MEDIUM }?.valueAsString,
16+
deviceType = metadata.find { it.key == WCMetaData.OrderAttributionInfoKeys.DEVICE_TYPE }?.valueAsString,
1917
sessionPageViews = metadata.find {
2018
it.key == WCMetaData.OrderAttributionInfoKeys.SESSION_PAGE_VIEWS
21-
}?.value
19+
}?.valueAsString
2220
)
2321
}

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/OrderEntity.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.wordpress.android.fluxc.model
33
import androidx.room.ColumnInfo
44
import androidx.room.Entity
55
import androidx.room.Index
6+
import androidx.room.TypeConverters
67
import com.google.gson.Gson
78
import com.google.gson.reflect.TypeToken
89
import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId
@@ -12,6 +13,7 @@ import org.wordpress.android.fluxc.model.order.LineItem
1213
import org.wordpress.android.fluxc.model.order.OrderAddress
1314
import org.wordpress.android.fluxc.model.order.ShippingLine
1415
import org.wordpress.android.fluxc.model.order.TaxLine
16+
import org.wordpress.android.fluxc.persistence.converters.WCMetaDataConverter
1517
import java.math.BigDecimal
1618

1719
@Entity(
@@ -71,7 +73,9 @@ data class OrderEntity(
7173
val taxLines: String = "",
7274
@ColumnInfo(name = "couponLines", defaultValue = "")
7375
val couponLines: String = "",
74-
val metaData: String = "", // this is a small subset of the metadata, see OrderMetaDataEntity for full metadata
76+
// this is a small subset of the metadata, see OrderMetaDataEntity for full metadata
77+
@field:TypeConverters(WCMetaDataConverter::class)
78+
val metaData: List<WCMetaData> = emptyList(),
7579
@ColumnInfo(name = "paymentUrl", defaultValue = "")
7680
val paymentUrl: String = "",
7781
@ColumnInfo(name = "isEditable", defaultValue = "1")
@@ -157,13 +161,5 @@ data class OrderEntity(
157161
return gson.fromJson(taxLines, responseType) as? List<TaxLine> ?: emptyList()
158162
}
159163

160-
/**
161-
* Deserializes the JSON contained in [metaData] into a list of [WCMetaData] objects.
162-
*/
163-
fun getMetaDataList(): List<WCMetaData> {
164-
val responseType = object : TypeToken<List<WCMetaData>>() {}.type
165-
return gson.fromJson(metaData, responseType) as? List<WCMetaData> ?: emptyList()
166-
}
167-
168164
fun isMultiShippingLinesAvailable() = getShippingLineList().size > 1
169165
}

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/WCMetaData.kt

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,85 @@ package org.wordpress.android.fluxc.model
22

33
import com.google.gson.JsonArray
44
import com.google.gson.JsonObject
5+
import com.google.gson.annotations.JsonAdapter
56
import com.google.gson.annotations.SerializedName
67

78
data class WCMetaData(
8-
@SerializedName(ID) val id: Long,
9-
@SerializedName(KEY) val key: String?,
10-
@SerializedName(VALUE) val value: Any,
11-
@SerializedName(DISPLAY_KEY) val displayKey: String?,
12-
@SerializedName(DISPLAY_VALUE) val displayValue: Any?
9+
@SerializedName(ID)
10+
val id: Long,
11+
@SerializedName(KEY)
12+
val key: String,
13+
@JsonAdapter(WCMetaDataValue.WCMetaDataValueJsonAdapter::class)
14+
@SerializedName(VALUE)
15+
val value: WCMetaDataValue,
16+
@SerializedName(DISPLAY_KEY)
17+
val displayKey: String? = null,
18+
@JsonAdapter(WCMetaDataValue.WCMetaDataValueJsonAdapter::class)
19+
@SerializedName(DISPLAY_VALUE)
20+
val displayValue: WCMetaDataValue? = null
1321
) {
22+
constructor(id: Long, key: String, value: String) : this(id, key, WCMetaDataValue.fromRawString(value))
23+
24+
/**
25+
* Verify if the Metadata key is not null or a internal store attribute
26+
* @return false if the `key` starts with the `_` character
27+
* @return true otherwise
28+
*/
29+
val isDisplayable
30+
get() = key.startsWith('_').not()
31+
32+
val valueAsString: String
33+
get() = value.stringValue.orEmpty()
34+
35+
val valueStrippedHtml: String
36+
get() = valueAsString.replace(htmlRegex, "")
37+
38+
val isHtml: Boolean
39+
get() = valueAsString.contains(htmlRegex)
40+
41+
val isJson: Boolean
42+
get() = value is WCMetaDataValue.JsonObjectValue || value is WCMetaDataValue.JsonArrayValue
43+
44+
internal fun toJson(): JsonObject {
45+
return JsonObject().also {
46+
it.addProperty(ID, id)
47+
it.addProperty(KEY, key)
48+
it.add(VALUE, value.jsonValue)
49+
displayKey?.let { key -> it.addProperty(DISPLAY_KEY, key) }
50+
displayValue?.let { value -> it.add(DISPLAY_VALUE, value.jsonValue) }
51+
}
52+
}
53+
1454
companion object {
15-
const val ID = "id"
55+
private const val ID = "id"
1656
const val KEY = "key"
1757
const val VALUE = "value"
18-
const val DISPLAY_KEY = "display_key"
19-
const val DISPLAY_VALUE = "display_value"
58+
private const val DISPLAY_KEY = "display_key"
59+
private const val DISPLAY_VALUE = "display_value"
60+
61+
private val htmlRegex by lazy {
62+
Regex("<[^>]+>")
63+
}
64+
2065
val SUPPORTED_KEYS: Set<String> = buildSet {
2166
add(SubscriptionMetadataKeys.SUBSCRIPTION_RENEWAL)
2267
add(BundleMetadataKeys.BUNDLED_ITEM_ID)
2368
addAll(OrderAttributionInfoKeys.ALL_KEYS)
2469
}
2570

71+
internal fun fromJson(json: JsonObject): WCMetaData {
72+
return WCMetaData(
73+
id = json[ID].asLong,
74+
key = json[KEY].asString,
75+
value = WCMetaDataValue.fromJsonElement(json[VALUE]),
76+
displayKey = json[DISPLAY_KEY]?.asString,
77+
displayValue = json[DISPLAY_VALUE]?.let { WCMetaDataValue.fromJsonElement(it) }
78+
)
79+
}
80+
2681
fun addAsMetadata(metadata: JsonArray, key: String, value: String) {
2782
val item = JsonObject().also {
83+
it.addProperty(ID, 0)
2884
it.addProperty(KEY, key)
2985
it.addProperty(VALUE, value)
3086
}
@@ -61,3 +117,6 @@ data class WCMetaData(
61117
)
62118
}
63119
}
120+
121+
operator fun List<WCMetaData>.get(key: String) = firstOrNull { it.key == key }
122+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package org.wordpress.android.fluxc.model
2+
3+
import com.google.gson.JsonArray
4+
import com.google.gson.JsonDeserializationContext
5+
import com.google.gson.JsonDeserializer
6+
import com.google.gson.JsonElement
7+
import com.google.gson.JsonNull
8+
import com.google.gson.JsonObject
9+
import com.google.gson.JsonParser
10+
import com.google.gson.JsonPrimitive
11+
import com.google.gson.JsonSerializationContext
12+
import com.google.gson.JsonSerializer
13+
import java.lang.reflect.Type
14+
15+
sealed class WCMetaDataValue {
16+
abstract val isPrimitive: Boolean
17+
abstract val stringValue: String?
18+
internal abstract val jsonValue: JsonElement
19+
20+
override fun toString(): String = stringValue.toString()
21+
22+
data class StringValue(override val stringValue: String?) : WCMetaDataValue() {
23+
override val isPrimitive: Boolean
24+
get() = true
25+
override val jsonValue: JsonElement
26+
get() = stringValue?.let { JsonPrimitive(it) } ?: JsonNull.INSTANCE
27+
}
28+
29+
data class NumberValue(val number: Number) : WCMetaDataValue() {
30+
override val isPrimitive: Boolean
31+
get() = true
32+
override val stringValue: String
33+
get() = number.toString()
34+
override val jsonValue: JsonElement
35+
get() = JsonPrimitive(number)
36+
}
37+
38+
data class BooleanValue(val boolean: Boolean) : WCMetaDataValue() {
39+
override val isPrimitive: Boolean
40+
get() = true
41+
override val stringValue: String
42+
get() = boolean.toString()
43+
override val jsonValue: JsonElement
44+
get() = JsonPrimitive(boolean)
45+
}
46+
47+
data class JsonObjectValue(override val jsonValue: JsonObject) : WCMetaDataValue() {
48+
override val isPrimitive: Boolean
49+
get() = false
50+
override val stringValue: String
51+
get() = jsonValue.toString()
52+
}
53+
54+
data class JsonArrayValue(override val jsonValue: JsonArray) : WCMetaDataValue() {
55+
override val isPrimitive: Boolean
56+
get() = false
57+
override val stringValue: String
58+
get() = jsonValue.toString()
59+
}
60+
61+
internal class WCMetaDataValueJsonAdapter : JsonDeserializer<WCMetaDataValue>,
62+
JsonSerializer<WCMetaDataValue> {
63+
override fun deserialize(
64+
json: JsonElement?,
65+
typeOfT: Type?,
66+
context: JsonDeserializationContext?
67+
): WCMetaDataValue? {
68+
return json?.let { fromJsonElement(it) }
69+
}
70+
71+
override fun serialize(
72+
src: WCMetaDataValue?,
73+
typeOfSrc: Type?,
74+
context: JsonSerializationContext?
75+
): JsonElement {
76+
return src?.jsonValue ?: JsonNull.INSTANCE
77+
}
78+
}
79+
80+
companion object {
81+
internal fun fromJsonElement(element: JsonElement): WCMetaDataValue {
82+
return when {
83+
element.isJsonPrimitive -> {
84+
val primitive = element.asJsonPrimitive
85+
when {
86+
primitive.isBoolean -> BooleanValue(primitive.asBoolean)
87+
primitive.isNumber -> NumberValue(primitive.asNumber)
88+
else -> StringValue(primitive.asString)
89+
}
90+
}
91+
92+
element.isJsonObject -> JsonObjectValue(element.asJsonObject)
93+
element.isJsonArray -> JsonArrayValue(element.asJsonArray)
94+
element.isJsonNull -> StringValue(null)
95+
else -> StringValue(element.toString())
96+
}
97+
}
98+
99+
fun fromRawString(value: String): WCMetaDataValue =
100+
runCatching { JsonParser().parse(value) }
101+
.getOrElse { JsonPrimitive(value) }
102+
.let { fromJsonElement(it) }
103+
}
104+
}

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductModel.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.google.gson.JsonArray
55
import com.google.gson.JsonElement
66
import com.google.gson.JsonObject
77
import com.google.gson.JsonParseException
8+
import com.google.gson.JsonParser
89
import com.yarolegovich.wellsql.core.Identifiable
910
import com.yarolegovich.wellsql.core.annotation.Column
1011
import com.yarolegovich.wellsql.core.annotation.PrimaryKey
@@ -124,10 +125,14 @@ data class WCProductModel(@PrimaryKey @Column private var id: Int = 0) : Identif
124125
val attributeList: Array<ProductAttribute>
125126
get() = Gson().fromJson(attributes, Array<ProductAttribute>::class.java) ?: emptyArray()
126127

128+
val parsedMetaData: List<WCMetaData>
129+
get() = runCatching { JsonParser().parse(metadata).asJsonArray }
130+
.getOrNull()
131+
?.mapNotNull { element -> (element as? JsonObject)?.let { WCMetaData.fromJson(it) } }
132+
?: emptyList()
133+
127134
val addons: Array<RemoteAddonDto>?
128-
get() = Gson().fromJson(metadata, Array<WCMetaData>::class.java)
129-
?.find { it.key == ADDONS_METADATA_KEY }
130-
?.addons
135+
get() = parsedMetaData.get(ADDONS_METADATA_KEY)?.addons
131136

132137
val isConfigurable: Boolean
133138
get() = when (type) {
@@ -146,8 +151,7 @@ data class WCProductModel(@PrimaryKey @Column private var id: Int = 0) : Identif
146151
get() =
147152
try {
148153
Gson().run {
149-
val addonListJson = toJson(value)
150-
fromJson(addonListJson, Array<RemoteAddonDto>::class.java)
154+
fromJson(value.jsonValue, Array<RemoteAddonDto>::class.java)
151155
}
152156
} catch (ex: Exception) {
153157
null

plugins/woocommerce/src/main/kotlin/org/wordpress/android/fluxc/model/order/LineItem.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.wordpress.android.fluxc.model.order
33
import com.google.gson.annotations.JsonAdapter
44
import com.google.gson.annotations.SerializedName
55
import org.wordpress.android.fluxc.model.WCMetaData
6+
import org.wordpress.android.fluxc.model.get
67
import org.wordpress.android.fluxc.utils.JsonElementToLongSerializerDeserializer
78

89
data class LineItem(
@@ -33,15 +34,15 @@ data class LineItem(
3334

3435
fun getAttributeList(): List<Attribute> {
3536
return metaData?.filter {
36-
it.displayKey is String && it.displayValue is String
37+
it.displayKey is String && it.displayValue?.isPrimitive == true
3738
}?.map {
38-
Attribute(it.displayKey, it.displayValue as String)
39+
Attribute(it.displayKey, it.displayValue?.stringValue)
3940
} ?: emptyList()
4041
}
4142

4243
val configurationKey
4344
get() = bundledBy.takeIf { it.isNullOrBlank().not() }
44-
?.let { metaData }
45-
?.find { it.key == WCMetaData.BundleMetadataKeys.BUNDLED_ITEM_ID }
46-
?.value.toString().toLongOrNull()
45+
?.let { metaData?.get(WCMetaData.BundleMetadataKeys.BUNDLED_ITEM_ID) }
46+
?.valueAsString
47+
?.toLongOrNull()
4748
}

0 commit comments

Comments
 (0)