diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index 92422702..eb67a0eb 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -1 +1 @@ -coverage45.8% \ No newline at end of file +coverage45.7% \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f77082..d1f68eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The changelog for `Superwall`. Also see the [releases](https://github.com/superw ## Unreleased - Fix prices not showing when product/offers are fetched from cache +- Fix a JSON null in placement parameters or user attributes reaching audience filters as the text `"null"`, so a filter checking whether a field is null never matched. ## 2.8.2 diff --git a/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/cel/SuperscriptEvaluator.kt b/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/cel/SuperscriptEvaluator.kt index 447b8538..38caed6a 100644 --- a/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/cel/SuperscriptEvaluator.kt +++ b/superwall/src/main/java/com/superwall/sdk/paywall/presentation/rule_logic/cel/SuperscriptEvaluator.kt @@ -27,6 +27,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.boolean @@ -246,6 +247,11 @@ internal fun Any.toPassableValue(): PassableValue = private fun JsonElement.toPassableValue(): PassableValue = when (this) { + // Checked before JsonPrimitive, which JsonNull is a kind of. Without this a + // JSON null falls past every primitive branch and comes out as the string + // "null", so an audience filter comparing the field to null never matches. + is JsonNull -> PassableValue.NullValue + is JsonObject -> PassableValue.MapValue( this.mapValues { (_, value) -> value.toPassableValue() }.toMap(), diff --git a/superwall/src/test/java/com/superwall/sdk/paywall/presentation/rule_logic/JsonElementToPassableValueTest.kt b/superwall/src/test/java/com/superwall/sdk/paywall/presentation/rule_logic/JsonElementToPassableValueTest.kt index 0acec103..e5db9c24 100644 --- a/superwall/src/test/java/com/superwall/sdk/paywall/presentation/rule_logic/JsonElementToPassableValueTest.kt +++ b/superwall/src/test/java/com/superwall/sdk/paywall/presentation/rule_logic/JsonElementToPassableValueTest.kt @@ -64,6 +64,14 @@ class JsonElementToPassableValueTest { assertEquals(false, (passableFalseValue as PassableValue.BoolValue).value) } + @Test + fun `test JsonNull conversion to PassableValue`() { + // JsonNull is a JsonPrimitive, so without an explicit branch it falls past + // every primitive check and comes out as the string "null". An audience + // filter asking `field == null` would then never match. + assertTrue(JsonNull.toPassableValue() is PassableValue.NullValue) + } + @Test fun `test JsonObject conversion to PassableValue`() { val jsonObject = @@ -94,8 +102,7 @@ class JsonElementToPassableValueTest { assertTrue(resultMap["boolean"] is PassableValue.BoolValue) assertEquals(true, (resultMap["boolean"] as PassableValue.BoolValue).value) - assertTrue(resultMap["null"] is PassableValue.StringValue) - assertEquals("null", (resultMap["null"] as PassableValue.StringValue).value) + assertTrue(resultMap["null"] is PassableValue.NullValue) } @Test @@ -128,8 +135,7 @@ class JsonElementToPassableValueTest { assertTrue(resultList[3] is PassableValue.BoolValue) assertEquals(true, (resultList[3] as PassableValue.BoolValue).value) - assertTrue(resultList[4] is PassableValue.StringValue) - assertEquals("null", (resultList[4] as PassableValue.StringValue).value) + assertTrue(resultList[4] is PassableValue.NullValue) } @Test