Skip to content

Commit 05355d2

Browse files
fix(util): round-trip null Value in ValueOrCompletion serialization (#47)
A materialized Flow<T?> that emits null produces Value(null), but none of the serializers could round-trip it: - Fory force-cast the read ref to non-null Any (NullPointerException). - The Jackson 2/3 deserializers conflated an absent value field with a present-but-null one, throwing "Missing value field" on "value": null. Drop the Fory non-null cast, and in both Jackson deserializers throw only when the field is genuinely absent, treating a present null node as null. Adds a null-Value round-trip test to each serialization spec.
1 parent 385f1f8 commit 05355d2

6 files changed

Lines changed: 31 additions & 6 deletions

File tree

util/src/main/kotlin/com/caplin/integration/datasourcex/util/serialization/fory/ValueOrCompletionSerializer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ internal class ValueOrCompletionSerializer(
3838
override fun read(readContext: ReadContext): ValueOrCompletion<*> {
3939
return when (Type.entries[readContext.readByte().toInt()]) {
4040
Type.VALUE -> {
41-
val value = readContext.readRef() as Any
41+
val value = readContext.readRef()
4242
ValueOrCompletion.Value(value)
4343
}
4444
Type.COMPLETION -> {

util/src/main/kotlin/com/caplin/integration/datasourcex/util/serialization/jackson2/ValueOrCompletionDeserializer.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ internal class ValueOrCompletionDeserializer :
1616
?: throw JsonMappingException.from(p, "Missing type field for ValueOrCompletion")
1717
return when (type) {
1818
"value" -> {
19-
val value =
20-
node.get("value")?.let { p.codec.treeToValue(it, Any::class.java) }
19+
val valueNode =
20+
node.get("value")
2121
?: throw JsonMappingException.from(p, "Missing value field for value type")
22+
val value = if (valueNode.isNull) null else p.codec.treeToValue(valueNode, Any::class.java)
2223
ValueOrCompletion.Value(value)
2324
}
2425
"completion" -> {

util/src/main/kotlin/com/caplin/integration/datasourcex/util/serialization/jackson3/ValueOrCompletionDeserializer.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ internal class ValueOrCompletionDeserializer :
1515
?: throw DatabindException.from(p, "Missing type field for ValueOrCompletion")
1616
return when (type) {
1717
"value" -> {
18-
val value =
19-
node.get("value")?.let { ctxt.readTreeAsValue(it, Any::class.java) }
18+
val valueNode =
19+
node.get("value")
2020
?: throw DatabindException.from(p, "Missing value field for value type")
21+
val value = if (valueNode.isNull) null else ctxt.readTreeAsValue(valueNode, Any::class.java)
2122
ValueOrCompletion.Value(value)
2223
}
2324
"completion" -> {

util/src/test/kotlin/com/caplin/integration/datasourcex/util/serialization/fory/ValueOrCompletionSerializationTest.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ class ValueOrCompletionSerializationTest :
5050
deserialized shouldBe event
5151
}
5252

53+
test("Value (null)") {
54+
val event = ValueOrCompletion.Value<String?>(null)
55+
val bytes = fory.serialize(event)
56+
val deserialized = fory.deserialize(bytes)
57+
deserialized shouldBe event
58+
}
59+
5360
test("Completion") {
5461
val event = ValueOrCompletion.Completion(null)
5562
val bytes = fory.serialize(event)
@@ -92,7 +99,7 @@ class ValueOrCompletionSerializationTest :
9299
val bytes = foryNoType.serialize(event)
93100
val deserialized = foryNoType.deserialize(bytes) as ValueOrCompletion.Completion
94101
deserialized.throwable.shouldBeInstanceOf<RuntimeException>()
95-
deserialized.throwable?.message shouldBe "aah"
102+
deserialized.throwable.message shouldBe "aah"
96103
}
97104
}
98105
},

util/src/test/kotlin/com/caplin/integration/datasourcex/util/serialization/jackson2/ValueOrCompletionSerializationTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ class ValueOrCompletionSerializationTest :
2020
deserialized shouldBe event
2121
}
2222

23+
test("Value (null)") {
24+
val event: ValueOrCompletion<String?> = ValueOrCompletion.Value(null)
25+
val json = mapper.writeValueAsString(event)
26+
val deserialized =
27+
mapper.readValue(json, object : TypeReference<ValueOrCompletion<String?>>() {})
28+
deserialized shouldBe event
29+
}
30+
2331
test("Completion (null)") {
2432
val event: ValueOrCompletion<String> = ValueOrCompletion.Completion(null)
2533
val json = mapper.writeValueAsString(event)

util/src/test/kotlin/com/caplin/integration/datasourcex/util/serialization/jackson3/ValueOrCompletionSerializationTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ class ValueOrCompletionSerializationTest :
1919
deserialized shouldBe event
2020
}
2121

22+
test("Value (null)") {
23+
val event: ValueOrCompletion<String?> = ValueOrCompletion.Value(null)
24+
val json = mapper.writeValueAsString(event)
25+
val deserialized =
26+
mapper.readValue(json, object : TypeReference<ValueOrCompletion<String?>>() {})
27+
deserialized shouldBe event
28+
}
29+
2230
test("Completion (null)") {
2331
val event: ValueOrCompletion<String> = ValueOrCompletion.Completion(null)
2432
val json = mapper.writeValueAsString(event)

0 commit comments

Comments
 (0)