Skip to content

Commit 83ff3c0

Browse files
committed
[ECO-5386] Implement JSON and MessagePack serialization for ObjectMessage
1. Added jackson-dataformat-msgpack 0.8.11 dependency for automatic msgpack handling 2. Implemented JSON serialization via ObjectMessage.toJsonObject() and JsonObject.toObjectMessage() extensions 3. Implemented MessagePack serialization via ObjectMessage.writeTo() and MessageUnpacker.readObjectMessage() extensions 4. Added @SerializedName("object") annotation for proper field naming consistency
1 parent d227718 commit 83ff3c0

4 files changed

Lines changed: 75 additions & 10 deletions

File tree

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mockk = "1.14.2"
2525
turbine = "1.2.0"
2626
ktor = "3.1.3"
2727
jetbrains-annoations = "26.0.2"
28+
jackson-msgpack = "0.8.11" # Compatible with msgpack-core 0.8.11
2829

2930
[libraries]
3031
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
@@ -54,6 +55,7 @@ turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine
5455
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
5556
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
5657
jetbrains = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annoations" }
58+
jackson-msgpack = { group = "org.msgpack", name = "jackson-dataformat-msgpack", version.ref = "jackson-msgpack" }
5759

5860
[bundles]
5961
common = ["msgpack", "vcdiff-core"]

live-objects/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
implementation(project(":java"))
1414
implementation(libs.bundles.common)
1515
implementation(libs.coroutine.core)
16+
implementation(libs.jackson.msgpack)
1617

1718
testImplementation(kotlin("test"))
1819
testImplementation(libs.bundles.kotlin.tests)

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.ably.lib.objects
22

3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import com.google.gson.annotations.SerializedName
5+
36
/**
47
* An enum class representing the different actions that can be performed on an object.
58
* Spec: OOP2
@@ -318,6 +321,8 @@ internal data class ObjectMessage(
318321
* the `ProtocolMessage` encapsulating it is `OBJECT_SYNC`.
319322
* Spec: OM2g
320323
*/
324+
@SerializedName("object")
325+
@JsonProperty("object")
321326
val objectState: ObjectState? = null,
322327

323328
/**
Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,88 @@
1+
@file:Suppress("UNCHECKED_CAST")
2+
13
package io.ably.lib.objects
24

3-
import com.google.gson.Gson
4-
import com.google.gson.GsonBuilder
5-
import com.google.gson.JsonArray
5+
import com.fasterxml.jackson.databind.ObjectMapper
6+
import com.google.gson.*
7+
import org.msgpack.core.MessagePack
68
import org.msgpack.core.MessagePacker
79
import org.msgpack.core.MessageUnpacker
10+
import org.msgpack.jackson.dataformat.MessagePackFactory
11+
12+
// Gson instance for JSON serialization/deserialization
13+
internal val gson: Gson = GsonBuilder().create()
814

9-
internal val gson: Gson = createGsonSerializer()
15+
// Jackson ObjectMapper for MessagePack serialization (respects @JsonProperty annotations)
16+
// Caches type metadata and serializers for ObjectMessage class after first use, so super fast!!
17+
private val msgpackMapper = ObjectMapper(MessagePackFactory())
18+
19+
internal fun ObjectMessage.toJsonObject(): JsonObject {
20+
return gson.toJsonTree(this).asJsonObject
21+
}
1022

11-
private fun createGsonSerializer(): Gson {
12-
return GsonBuilder().create() // Do not call serializeNulls() to omit null values
23+
internal fun JsonObject.toObjectMessage(): ObjectMessage {
24+
return gson.fromJson(this, ObjectMessage::class.java)
1325
}
1426

27+
internal fun ObjectMessage.writeTo(packer: MessagePacker) {
28+
// Jackson automatically creates the correct msgpack map structure
29+
val msgpackBytes = msgpackMapper.writeValueAsBytes(this)
30+
31+
// Parse the msgpack bytes to get the structured value
32+
val tempUnpacker = MessagePack.newDefaultUnpacker(msgpackBytes)
33+
val msgpackValue = tempUnpacker.unpackValue()
34+
tempUnpacker.close()
35+
36+
// Pack the structured value using the provided packer
37+
packer.packValue(msgpackValue)
38+
}
39+
40+
internal fun MessageUnpacker.readObjectMessage(): ObjectMessage {
41+
// Read the msgpack value from the unpacker
42+
val msgpackValue = this.unpackValue()
43+
44+
// Convert the msgpack value back to bytes
45+
val tempPacker = MessagePack.newDefaultBufferPacker()
46+
tempPacker.packValue(msgpackValue)
47+
val msgpackBytes = tempPacker.toByteArray()
48+
tempPacker.close()
49+
50+
// Let Jackson deserialize the msgpack bytes back to ObjectMessage
51+
return msgpackMapper.readValue(msgpackBytes, ObjectMessage::class.java)
52+
}
53+
54+
/**
55+
* Default implementation of {@link LiveObjectSerializer} that handles serialization/deserialization
56+
* of ObjectMessage arrays for both JSON and MessagePack formats using Jackson and Gson.
57+
* Dynamically loaded by LiveObjectsHelper#getLiveObjectSerializer() to avoid hard dependencies.
58+
*/
59+
@Suppress("unused") // Used via reflection in LiveObjectsHelper
1560
internal class DefaultLiveObjectSerializer : LiveObjectSerializer {
61+
1662
override fun readMsgpackArray(unpacker: MessageUnpacker): Array<Any> {
17-
TODO("Not yet implemented")
63+
val objectMessagesCount = unpacker.unpackArrayHeader()
64+
return Array(objectMessagesCount) { unpacker.readObjectMessage() }
1865
}
1966

2067
override fun writeMsgpackArray(objects: Array<out Any>?, packer: MessagePacker) {
21-
TODO("Not yet implemented")
68+
val objectMessages: Array<ObjectMessage> = objects as Array<ObjectMessage>
69+
packer.packArrayHeader(objectMessages.size)
70+
objectMessages.forEach { it.writeTo(packer) }
2271
}
2372

2473
override fun readFromJsonArray(json: JsonArray): Array<Any> {
25-
TODO("Not yet implemented")
74+
return json.map { element ->
75+
if (element.isJsonObject) element.asJsonObject.toObjectMessage()
76+
else throw JsonParseException("Expected JsonObject, but found: $element")
77+
}.toTypedArray()
2678
}
2779

2880
override fun asJsonArray(objects: Array<out Any>?): JsonArray {
29-
TODO("Not yet implemented")
81+
val objectMessages: Array<ObjectMessage> = objects as Array<ObjectMessage>
82+
val jsonArray = JsonArray()
83+
for (objectMessage in objectMessages) {
84+
jsonArray.add(objectMessage.toJsonObject())
85+
}
86+
return jsonArray
3087
}
3188
}

0 commit comments

Comments
 (0)