Skip to content

Commit 10a09e3

Browse files
committed
[ECO-5386] Implemented custom ObjectDataJsonSerializer for ObjectData type
1 parent 9d19c2b commit 10a09e3

3 files changed

Lines changed: 71 additions & 23 deletions

File tree

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

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

33
import com.fasterxml.jackson.annotation.JsonProperty
4+
import com.google.gson.JsonArray
5+
import com.google.gson.JsonObject
6+
import com.google.gson.annotations.JsonAdapter
47
import com.google.gson.annotations.SerializedName
58

69
/**
@@ -28,19 +31,14 @@ internal enum class MapSemantics(val code: Int) {
2831
* An ObjectData represents a value in an object on a channel.
2932
* Spec: OD1
3033
*/
34+
@JsonAdapter(ObjectDataJsonSerializer::class)
3135
internal data class ObjectData(
3236
/**
3337
* A reference to another object, used to support composable object structures.
3438
* Spec: OD2a
3539
*/
3640
val objectId: String? = null,
3741

38-
/**
39-
* Can be set by the client to indicate that value in `string` or `bytes` field have an encoding.
40-
* Spec: OD2b
41-
*/
42-
val encoding: String? = null,
43-
4442
/**
4543
* String, number, boolean or binary - a concrete value of the object
4644
* Spec: OD2c
@@ -49,20 +47,27 @@ internal data class ObjectData(
4947
)
5048

5149
/**
52-
* Represents a value that can be a String, Number, Boolean or Binary.
50+
* Represents a value that can be a String, Number, Boolean, Binary, JsonObject, or JsonArray.
5351
* Performs a type check on initialization.
5452
* Spec: OD2c
5553
*/
5654
internal data class ObjectValue(
5755
/**
58-
* The concrete value of the object. Can be a String, Number, Boolean or Binary.
56+
* The concrete value of the object. Can be a String, Number, Boolean, Binary, JsonObject, or JsonArray.
5957
* Spec: OD2c
6058
*/
6159
val value: Any,
6260
) {
6361
init {
64-
require(value is String || value is Number || value is Boolean || value is Binary) {
65-
"value must be String, Number, Boolean or Binary"
62+
require(
63+
value is String ||
64+
value is Number ||
65+
value is Boolean ||
66+
value is Binary ||
67+
value is JsonObject ||
68+
value is JsonArray
69+
) {
70+
"value must be String, Number, Boolean, Binary, JsonObject or JsonArray"
6671
}
6772
}
6873
}
@@ -204,18 +209,13 @@ internal data class ObjectOperation(
204209
val nonce: String? = null,
205210

206211
/**
207-
* The initial value bytes for the object. These bytes should be used along with the nonce
208-
* and timestamp to create the object ID. Frontdoor will use this to verify the object ID.
209-
* After verification the bytes will be decoded into the Map or Counter objects and
210-
* the initialValue, nonce, and initialValueEncoding will be removed.
212+
* The initial value for the object, encoded as a base64 string.
213+
* This value should be used along with the nonce and timestamp to create the object ID.
214+
* Frontdoor will use this to verify the object ID. After verification, the value will be
215+
* decoded into the Map or Counter objects and the initialValue, nonce, and initialValueEncoding will be removed.
211216
* Spec: OOP3h
212217
*/
213-
val initialValue: Binary? = null,
214-
215-
/** The initial value encoding defines how the initialValue should be interpreted.
216-
* Spec: OOP3i
217-
*/
218-
val initialValueEncoding: ProtocolMessageFormat? = null
218+
val initialValue: String? = null,
219219
)
220220

221221
/**

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import org.msgpack.core.MessagePack
88
import org.msgpack.core.MessagePacker
99
import org.msgpack.core.MessageUnpacker
1010
import org.msgpack.jackson.dataformat.MessagePackFactory
11+
import java.lang.reflect.Type
12+
import java.util.*
1113

1214
// Gson instance for JSON serialization/deserialization
1315
internal val gson: Gson = GsonBuilder().create()
@@ -86,3 +88,51 @@ internal class DefaultLiveObjectSerializer : LiveObjectSerializer {
8688
return jsonArray
8789
}
8890
}
91+
92+
internal class ObjectDataJsonSerializer : JsonSerializer<ObjectData>, JsonDeserializer<ObjectData> {
93+
override fun serialize(src: ObjectData?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
94+
val obj = JsonObject()
95+
src?.objectId?.let { obj.addProperty("objectId", it) }
96+
97+
src?.value?.let { value ->
98+
when (val v = value.value) {
99+
is Boolean -> obj.addProperty("boolean", v)
100+
is String -> obj.addProperty("string", v)
101+
is Number -> obj.addProperty("number", v)
102+
is Binary -> obj.addProperty("bytes", Base64.getEncoder().encodeToString(v.data))
103+
// Spec: OD4c5
104+
is JsonObject, is JsonArray -> {
105+
obj.addProperty("string", v.toString())
106+
obj.addProperty("encoding", "json")
107+
}
108+
}
109+
}
110+
return obj
111+
}
112+
113+
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): ObjectData {
114+
val obj = json?.asJsonObject ?: throw JsonParseException("Expected JsonObject")
115+
val objectId = if (obj.has("objectId")) obj.get("objectId").asString else null
116+
val encoding = if (obj.has("encoding")) obj.get("encoding").asString else null
117+
val value = when {
118+
obj.has("boolean") -> ObjectValue(obj.get("boolean").asBoolean)
119+
// Spec: OD5b3
120+
obj.has("string") && encoding == "json" -> {
121+
val jsonStr = obj.get("string").asString
122+
val parsed = JsonParser.parseString(jsonStr)
123+
ObjectValue(
124+
when {
125+
parsed.isJsonObject -> parsed.asJsonObject
126+
parsed.isJsonArray -> parsed.asJsonArray
127+
else -> throw JsonParseException("Invalid JSON string for encoding=json")
128+
}
129+
)
130+
}
131+
obj.has("string") -> ObjectValue(obj.get("string").asString)
132+
obj.has("number") -> ObjectValue(obj.get("number").asNumber)
133+
obj.has("bytes") -> ObjectValue(Binary(Base64.getDecoder().decode(obj.get("bytes").asString)))
134+
else -> null
135+
}
136+
return ObjectData(objectId, value)
137+
}
138+
}

live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ class ObjectMessageSizeTest {
4545
key = "mapKey", // Size: 6 bytes (UTF-8 byte length)
4646
data = ObjectData(
4747
objectId = "ref_obj", // Not counted in data size
48-
encoding = "utf-8", // Not counted in data size
4948
value = ObjectValue("sample") // Size: 6 bytes (UTF-8 byte length)
5049
) // Total ObjectData size: 6 bytes
5150
), // Total ObjectMapOp size: 6 + 6 = 12 bytes
@@ -80,8 +79,7 @@ class ObjectMessageSizeTest {
8079
), // Total ObjectCounter size: 8 bytes
8180

8281
nonce = "nonce123", // Not counted in operation size
83-
initialValue = Binary("initial".toByteArray()), // Not counted in operation size
84-
initialValueEncoding = ProtocolMessageFormat.Json // Not counted in operation size
82+
initialValue = "some-value", // Not counted in operation size
8583
), // Total ObjectOperation size: 12 + 8 + 26 + 8 = 54 bytes
8684

8785
objectState = ObjectState(

0 commit comments

Comments
 (0)