Skip to content

Commit

Permalink
Prevent flaky gson deserialization (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
xinlili-statsig authored Jun 7, 2024
1 parent 0bc1afd commit 457f8da
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 8 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/statsig/androidsdk/DynamicConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class DynamicConfig(
apiDynamicConfig.value,
apiDynamicConfig.ruleID,
apiDynamicConfig.groupName,
apiDynamicConfig.secondaryExposures,
apiDynamicConfig.secondaryExposures ?: arrayOf(),
apiDynamicConfig.isUserInExperiment,
apiDynamicConfig.isExperimentActive,
apiDynamicConfig.isDeviceBased,
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/statsig/androidsdk/FeatureGate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class FeatureGate(
apiFeatureGate.value,
apiFeatureGate.ruleID,
apiFeatureGate.groupName,
apiFeatureGate.secondaryExposures,
apiFeatureGate.secondaryExposures ?: arrayOf(),
apiFeatureGate.idType,
)

Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/statsig/androidsdk/InitializeResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal data class APIFeatureGate(
@SerializedName("value") val value: Boolean = false,
@SerializedName("rule_id") val ruleID: String = "",
@SerializedName("group_name") val groupName: String? = null,
@SerializedName("secondary_exposures") val secondaryExposures: Array<Map<String, String>> = arrayOf(),
@SerializedName("secondary_exposures") val secondaryExposures: Array<Map<String, String>>? = arrayOf(),
@SerializedName("id_type") val idType: String? = null,
)

Expand All @@ -41,11 +41,11 @@ internal data class APIDynamicConfig(
@SerializedName("value") val value: Map<String, Any>,
@SerializedName("rule_id") val ruleID: String = "",
@SerializedName("group_name") val groupName: String? = null,
@SerializedName("secondary_exposures") val secondaryExposures: Array<Map<String, String>> = arrayOf(),
@SerializedName("undelegated_secondary_exposures") val undelegatedSecondaryExposures: Array<Map<String, String>> = arrayOf(),
@SerializedName("secondary_exposures") val secondaryExposures: Array<Map<String, String>>? = arrayOf(),
@SerializedName("undelegated_secondary_exposures") val undelegatedSecondaryExposures: Array<Map<String, String>>? = arrayOf(),
@SerializedName("is_device_based") val isDeviceBased: Boolean = false,
@SerializedName("is_user_in_experiment") val isUserInExperiment: Boolean = false,
@SerializedName("is_experiment_active") val isExperimentActive: Boolean = false,
@SerializedName("allocated_experiment_name") val allocatedExperimentName: String? = null,
@SerializedName("explicit_parameters") val explicitParameters: Array<String> = arrayOf(),
@SerializedName("explicit_parameters") val explicitParameters: Array<String>? = arrayOf(),
)
4 changes: 2 additions & 2 deletions src/main/java/com/statsig/androidsdk/Layer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class Layer internal constructor(
apiDynamicConfig.value,
apiDynamicConfig.ruleID,
apiDynamicConfig.groupName,
apiDynamicConfig.secondaryExposures,
apiDynamicConfig.undelegatedSecondaryExposures,
apiDynamicConfig.secondaryExposures ?: arrayOf(),
apiDynamicConfig.undelegatedSecondaryExposures ?: arrayOf(),
apiDynamicConfig.isUserInExperiment,
apiDynamicConfig.isExperimentActive,
apiDynamicConfig.isDeviceBased,
Expand Down
24 changes: 24 additions & 0 deletions src/test/java/com/statsig/androidsdk/SerializationTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.statsig.androidsdk

import com.google.gson.Gson
import org.junit.Test

/*
* Gson serialization and deserialization does not use default value
* set in data class when a field is missing
* */
class SerializationTest {
val gson = Gson()

@Test
fun testSerializeResponseWithIncomplete() {
val initializeResponseSkipFields = "{\"feature_gates\":{\"245595137\":{\"name\":\"245595137\",\"value\":true,\"rule_id\":\"1uj9J1jxY2jnBAChgGB1jR:0.00:35\",\"id_type\":\"userID\"}},\"dynamic_configs\":{\"2887220988\":{\"name\":\"2887220988\",\"value\":{\"num\": 13},\"rule_id\":\"prestart\",\"group\":\"prestart\",\"is_device_based\":false,\"id_type\":\"userID\",\"is_experiment_active\":true,\"is_user_in_experiment\":true}},\"layer_configs\":{},\"sdkParams\":{},\"has_updates\":true,\"time\":1717536742309,\"company_lcut\":1717536742309,\"hash_used\":\"djb2\"}"
val parsedResponse = gson.fromJson(initializeResponseSkipFields, InitializeResponse.SuccessfulInitializeResponse::class.java)
val gate = FeatureGate("some_gate", parsedResponse.featureGates!!.get("245595137")!!, EvaluationDetails(EvaluationReason.Error))
val config = DynamicConfig("some_config", parsedResponse.configs!!.get("2887220988")!!, EvaluationDetails(EvaluationReason.Error))
assert(gate.getValue())
assert(gate.getSecondaryExposures().isEmpty())
assert(config.getInt("num", 0) == 13)
assert(config.getSecondaryExposures().isEmpty())
}
}

0 comments on commit 457f8da

Please sign in to comment.