Skip to content

[AIT-1009] Implement Json and MsgPack serializers for path-based LiveObjects#1218

Merged
sacOO7 merged 7 commits into
feature/path-based-liveobjects-implementationfrom
chore/path-based-liveobjects-serialization
Jun 24, 2026
Merged

[AIT-1009] Implement Json and MsgPack serializers for path-based LiveObjects#1218
sacOO7 merged 7 commits into
feature/path-based-liveobjects-implementationfrom
chore/path-based-liveobjects-serialization

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the serialization layer for the path-based LiveObjects API (io.ably.lib.object), supporting both JSON and MessagePack encoding/decoding of object messages. This is the wire-format plumbing that sits underneath the path-based public API.

The design keeps the core lib free of any hard dependency on the optional LiveObjects plugin: the core declares interfaces and reflectively loads the implementation from the liveobjects module at runtime, returning null when the plugin is absent.

Core (lib)

  • ObjectSerializer — public interface declaring the four codec entry points (readMsgpackArray / writeMsgpackArray / readFromJsonArray / asJsonArray). The implementation is loaded reflectively and cached as a lazy singleton via a nested Holder (double-checked locking; retries until the plugin is present), exposed through the static ObjectSerializer.tryGet().
  • ObjectJsonSerializer — Gson JsonSerializer/JsonDeserializer for the protocol-message state field; delegates to the loaded ObjectSerializer, and no-ops gracefully (logs + JsonNull) when the plugin is unavailable.

Plugin (liveobjects)

  • DefaultObjectsSerializer — implements ObjectSerializer, (de)serializing arrays of object messages for JSON and MessagePack.
  • JsonSerialization — Gson setup: enum-by-code adapters and WireObjectDataJsonSerializer (handles the json-as-string encoding per OD4c5 and the "at least one value field" validation).
  • MsgpackSerialization — hand-rolled MessagePack pack/unpack for the full object model (operations, state, map/counter payloads, map entries, object data).

Wire model decoupling

The serializers operate on the WireObjectMessage model in io.ably.lib.object.message, so the new object package carries no dependency on the legacy io.ably.lib.objects package. WireObjectMessage carries the Gson annotations needed for wire-format fidelity — @SerializedName("object") on objectState (OM2g) and @JsonAdapter(WireObjectDataJsonSerializer) on WireObjectData.

Notes

  • Errors use the object package's objectStateError (ErrorInfo 500 / 92000).
  • No new test coverage for the path-based serializers yet — existing serialization tests still target the legacy package. Round-trip tests for the Wire* model are tracked as a follow-up.

Spec: OM*, OOP*, OD*, OMP*, OCN*, OST*, MCR*, MST*, MRM*, CCR*, CIN*, ODE*, MCL*

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced LiveObjectsPlugin to manage live objects per channel, including state-change handling and disposal.
    • Added JSON and MessagePack serialization/deserialization for live object wire data to support seamless state exchange.
    • Updated wire serialization mapping to align objectState with the expected JSON key.
    • Enabled automatic plugin initialization when a default implementation is available.

@sacOO7 sacOO7 changed the title Implemented Json and MsgPack serializers for path based liveobjects Implemente Json and MsgPack serializers for path based liveobjects Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9242e5b4-447e-44e5-b906-92561437b69b

📥 Commits

Reviewing files that changed from the base of the PR and between c489ac0 and 7ab9482.

📒 Files selected for processing (1)
  • lib/src/main/java/io/ably/lib/object/LiveObjectsPlugin.java

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

Adds LiveObjects plugin and serializer interfaces in the core library, then wires Gson and MessagePack serialization for LiveObjects wire message types in the liveobjects module.

Changes

LiveObjects Wire Serialization and Plugin Interface

Layer / File(s) Summary
ObjectSerializer interface and JSON bridge (lib)
lib/.../serialization/ObjectSerializer.java, lib/.../serialization/ObjectJsonSerializer.java
ObjectSerializer declares MessagePack and JSON array contracts; its nested Holder uses double-checked locking to reflectively load DefaultObjectsSerializer, returning null on failure. ObjectJsonSerializer is a Gson adapter that delegates to the singleton, logging warnings and falling back gracefully when the plugin is absent.
LiveObjectsPlugin interface and reflective factory (lib)
lib/.../object/LiveObjectsPlugin.java
Declares the LiveObjectsPlugin interface with per-channel RealtimeObjects access, protocol message and channel state change handling, and disposal methods. The static tryInitialize delegates to a nested Factory that reflectively loads DefaultLiveObjectsPlugin, wraps AblyRealtime in an Adapter, and returns null on reflection failures.
WireObjectMessage annotations and Gson instance
liveobjects/.../message/WireObjectMessage.kt, liveobjects/.../serialization/JsonSerialization.kt
Applies @JsonAdapter(WireObjectDataJsonSerializer::class) to WireObjectData and @SerializedName("object") to WireObjectMessage.objectState. Builds a shared Gson instance with EnumCodeTypeAdapter for WireObjectOperationAction and WireObjectsMapSemantics, and defines WireObjectMessage JSON helpers and WireObjectDataJsonSerializer.
DefaultObjectsSerializer (ObjectSerializer impl)
liveobjects/.../serialization/DefaultSerialization.kt
Implements DefaultObjectsSerializer as the concrete ObjectSerializer loaded reflectively by the core lib. Provides MessagePack array read/write via readObjectMessage/writeMsgpack and JSON array read/write via toObjectMessage/toJsonObject.
MessagePack codecs for top-level wire messages
liveobjects/.../serialization/MsgpackSerialization.kt (lines 1–407)
Implements Msgpack read/write for WireObjectMessage (null-omitting field count, Gson↔Msgpack extras conversion), WireObjectOperation (action code mapping, required field validation, empty-map delete/clear consumption), and WireObjectState (required objectId/siteTimeserials/tombstone, optional nested payloads).
MessagePack codecs for map/counter/entry/data primitives
liveobjects/.../serialization/MsgpackSerialization.kt (lines 408–916)
Adds read/write codecs for all remaining wire sub-types: WireMapCreate/Set/Remove, WireCounterCreate/Inc, WireMapCreateWithObjectId, WireCounterCreateWithObjectId, WireObjectsMap, WireObjectsCounter, WireObjectsMapEntry, and WireObjectData (base64↔binary bytes, JsonParser.parseString for JSON fields).

Sequence Diagram(s)

sequenceDiagram
  participant AblyRealtime
  participant LiveObjectsPlugin.Factory
  participant DefaultLiveObjectsPlugin
  AblyRealtime->>LiveObjectsPlugin.Factory: tryInitialize(ablyRealtime)
  LiveObjectsPlugin.Factory->>DefaultLiveObjectsPlugin: Class.forName + constructor(AblyClientAdapter)
  DefaultLiveObjectsPlugin-->>LiveObjectsPlugin.Factory: instance or null
  LiveObjectsPlugin.Factory-->>AblyRealtime: LiveObjectsPlugin or null
Loading
sequenceDiagram
  participant Gson
  participant ObjectJsonSerializer
  participant ObjectSerializer.Holder
  participant DefaultObjectsSerializer
  Gson->>ObjectJsonSerializer: serialize/deserialize Object[]
  ObjectJsonSerializer->>ObjectSerializer.Holder: tryGet()
  ObjectSerializer.Holder->>DefaultObjectsSerializer: reflective load + cache
  DefaultObjectsSerializer-->>ObjectSerializer.Holder: ObjectSerializer
  ObjectSerializer.Holder-->>ObjectJsonSerializer: ObjectSerializer
  ObjectJsonSerializer->>DefaultObjectsSerializer: readFromJsonArray()/asJsonArray()
  DefaultObjectsSerializer-->>Gson: JsonElement or Object[]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ably/ably-java#1217: Connects at the LiveObjects wire-message layer through the WireObjectMessage model and its Gson annotations.
  • ably/ably-java#1216: Consumes the LiveObjectsPlugin interface introduced here by wiring channel object support around plugin availability.

Poem

🐇 Hop, hop — the bytes align,
Gson and Msgpack now share one line.
A plugin whispers, “I’m here today,”
And wire objects dance in a tidy array.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: implementing JSON and MessagePack serializers for the path-based LiveObjects feature, which aligns with the primary purpose of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/path-based-liveobjects-serialization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 18, 2026 18:04 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/javadoc June 18, 2026 18:06 Inactive
- Implemented JsonSerializer annotation for better json handling
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 19, 2026 12:13 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/javadoc June 19, 2026 12:15 Inactive
Point the JSON and MsgPack serializers in io.ably.lib.object.serialization
at the new WireObjectMessage wire model instead of the legacy
io.ably.lib.objects.ObjectMessage, so the new `object` package has no
dependency on the legacy `objects` package.

- DefaultSerialization: implement the new ObjectSerializer interface and
  (de)serialize WireObjectMessage arrays (reflectively loaded via
  ObjectSerializer.Holder).
- Json/MsgpackSerialization: bind the Wire* types; replace legacy
  objectError with the object package's objectStateError (same 500/92000).
- WireObjectMessage: restore the gson annotations required for wire-format
  fidelity - @SerializedName("object") on objectState and
  @JsonAdapter(WireObjectDataJsonSerializer) on WireObjectData.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 19, 2026 12:22 Inactive
@sacOO7 sacOO7 changed the title Implemente Json and MsgPack serializers for path based liveobjects Implement Json and MsgPack serializers for path-based LiveObjects Jun 19, 2026
@sacOO7 sacOO7 changed the title Implement Json and MsgPack serializers for path-based LiveObjects [AIT-1009] Implement Json and MsgPack serializers for path-based LiveObjects Jun 19, 2026
@sacOO7 sacOO7 marked this pull request as ready for review June 19, 2026 12:23
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/javadoc June 19, 2026 12:23 Inactive
Fixes checkstyle AvoidStarImport violation on com.google.gson.*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 19, 2026 12:25 Inactive
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/path-based-liveobjects-serialization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 19, 2026 12:27 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/javadoc June 19, 2026 12:28 Inactive
@sacOO7 sacOO7 force-pushed the chore/path-based-liveobjects-serialization branch from b69d950 to bfa574f Compare June 19, 2026 12:28
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 19, 2026 12:28 Inactive
@sacOO7 sacOO7 requested a review from Copilot June 19, 2026 12:29
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/javadoc June 19, 2026 12:29 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the wire-format serialization layer for the path-based LiveObjects API (io.ably.lib.object), providing JSON and MessagePack codecs for the Wire* object-message model while keeping the core lib module decoupled from the optional liveobjects plugin via reflective loading.

Changes:

  • Introduces ObjectSerializer (+ ObjectJsonSerializer) in lib, with a lazy reflectively-loaded plugin implementation.
  • Adds DefaultObjectsSerializer plus JSON/MsgPack encoding/decoding for WireObjectMessage arrays in liveobjects.
  • Updates the Wire* model with Gson annotations needed for wire-format fidelity (e.g., "object" field name and WireObjectData JSON-as-string handling).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
liveobjects/src/main/kotlin/io/ably/lib/object/serialization/MsgpackSerialization.kt MessagePack (de)serialization for the path-based Wire* object model.
liveobjects/src/main/kotlin/io/ably/lib/object/serialization/JsonSerialization.kt Gson configuration + enum-by-code adapters + WireObjectData JSON-as-string adapter.
liveobjects/src/main/kotlin/io/ably/lib/object/serialization/DefaultSerialization.kt Plugin-side ObjectSerializer implementation for JSON/MsgPack arrays of WireObjectMessage.
liveobjects/src/main/kotlin/io/ably/lib/object/message/WireObjectMessage.kt Wire model updates: Gson annotations for "object" and WireObjectData adapter.
lib/src/main/java/io/ably/lib/object/serialization/ObjectSerializer.java Core-side serializer interface + reflective singleton loader for plugin implementation.
lib/src/main/java/io/ably/lib/object/serialization/ObjectJsonSerializer.java Gson serializer/deserializer for the protocol state field via ObjectSerializer.tryGet().
lib/src/main/java/io/ably/lib/object/LiveObjectsPlugin.java Core-side reflective plugin initializer interface for LiveObjects functionality.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions github-actions Bot temporarily deployed to staging/pull/1218/features June 22, 2026 07:45 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1218/javadoc June 22, 2026 07:47 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
liveobjects/src/main/kotlin/io/ably/lib/object/serialization/DefaultSerialization.kt (1)

21-36: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Validate element types before casting object arrays.

Line 22 and Line 35 use unchecked casts from Any to WireObjectMessage; malformed upstream arrays will fail with opaque ClassCastException. A typed check gives deterministic, actionable failures.

Suggested patch
   override fun writeMsgpackArray(objects: Array<out Any>, packer: MessagePacker) {
-    val objectMessages = objects.map { it as WireObjectMessage }
+    val objectMessages = objects.mapIndexed { index, value ->
+      value as? WireObjectMessage
+        ?: throw IllegalArgumentException("Expected WireObjectMessage at index $index, got ${value::class.java.name}")
+    }
     packer.packArrayHeader(objectMessages.size)
     objectMessages.forEach { it.writeMsgpack(packer) }
   }
@@
   override fun asJsonArray(objects: Array<out Any>): JsonArray {
-    val objectMessages = objects.map { it as WireObjectMessage }
+    val objectMessages = objects.mapIndexed { index, value ->
+      value as? WireObjectMessage
+        ?: throw IllegalArgumentException("Expected WireObjectMessage at index $index, got ${value::class.java.name}")
+    }
     val jsonArray = JsonArray()
     for (objectMessage in objectMessages) {
       jsonArray.add(objectMessage.toJsonObject())
     }
     return jsonArray
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@liveobjects/src/main/kotlin/io/ably/lib/object/serialization/DefaultSerialization.kt`
around lines 21 - 36, The methods writeMsgpackArray and asJsonArray both contain
unchecked casts to WireObjectMessage without validating the input types, which
can result in opaque ClassCastException errors. Add type validation before
casting in both methods by checking if each element is an instance of
WireObjectMessage using the is operator or as? operator, and throw a descriptive
exception with the actual element type if validation fails, similar to the
pattern already implemented in readFromJsonArray where you check isJsonObject
and throw JsonParseException.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/src/main/java/io/ably/lib/object/LiveObjectsPlugin.java`:
- Around line 96-105: The catch block in the plugin initialization code (around
the getDeclaredConstructor call and cast to LiveObjectsPlugin) does not handle
ClassCastException and LinkageError, which can be thrown during reflection and
type loading if the implementation exists but is incompatible. This causes the
optional LiveObjects feature to fail hard instead of gracefully degrading.
Extend the catch clause to also catch ClassCastException and LinkageError
alongside the existing exception types (ClassNotFoundException,
InstantiationException, IllegalAccessException, NoSuchMethodException,
InvocationTargetException) so the method returns null when any of these errors
occur, maintaining the resilience of this optional plugin.

In `@lib/src/main/java/io/ably/lib/object/serialization/ObjectSerializer.java`:
- Around line 83-90: The reflective loading of the ObjectSerializer class on
lines 84-85 can throw ClassCastException or LinkageError when the class exists
but is incompatible or incompletely linked, but these exceptions are not
currently caught in the catch block. Add ClassCastException and LinkageError to
the multi-catch exception handler in the ObjectSerializer reflective loading
section so these failures are gracefully handled the same way as other expected
exceptions, allowing the optional plugin to be disabled instead of crashing the
runtime message handling.

In
`@liveobjects/src/main/kotlin/io/ably/lib/object/serialization/MsgpackSerialization.kt`:
- Around line 356-407: Add validation for the required fields `siteTimeserials`
and `tombstone` in the readObjectState function. Currently these fields have
default values (empty map and false respectively), which prevents detection of
missing required fields per the WireObjectState spec. Introduce boolean flags to
track whether these fields were encountered during unpacking, then add
validation checks after the loop (similar to the existing objectId validation)
to throw an objectStateError if either siteTimeserials or tombstone is missing
from the wire payload.

---

Nitpick comments:
In
`@liveobjects/src/main/kotlin/io/ably/lib/object/serialization/DefaultSerialization.kt`:
- Around line 21-36: The methods writeMsgpackArray and asJsonArray both contain
unchecked casts to WireObjectMessage without validating the input types, which
can result in opaque ClassCastException errors. Add type validation before
casting in both methods by checking if each element is an instance of
WireObjectMessage using the is operator or as? operator, and throw a descriptive
exception with the actual element type if validation fails, similar to the
pattern already implemented in readFromJsonArray where you check isJsonObject
and throw JsonParseException.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c3a8978-b914-4dc6-ac31-9091b13905f6

📥 Commits

Reviewing files that changed from the base of the PR and between c8a283d and c489ac0.

📒 Files selected for processing (7)
  • lib/src/main/java/io/ably/lib/object/LiveObjectsPlugin.java
  • lib/src/main/java/io/ably/lib/object/serialization/ObjectJsonSerializer.java
  • lib/src/main/java/io/ably/lib/object/serialization/ObjectSerializer.java
  • liveobjects/src/main/kotlin/io/ably/lib/object/message/WireObjectMessage.kt
  • liveobjects/src/main/kotlin/io/ably/lib/object/serialization/DefaultSerialization.kt
  • liveobjects/src/main/kotlin/io/ably/lib/object/serialization/JsonSerialization.kt
  • liveobjects/src/main/kotlin/io/ably/lib/object/serialization/MsgpackSerialization.kt

Comment thread lib/src/main/java/io/ably/lib/object/LiveObjectsPlugin.java
@sacOO7 sacOO7 requested a review from ttypic June 22, 2026 08:17
Base automatically changed from chore/liveobjects-add-basic-implementation to feature/path-based-liveobjects-implementation June 22, 2026 08:22

@ttypic ttypic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sacOO7 sacOO7 merged commit e26ed39 into feature/path-based-liveobjects-implementation Jun 24, 2026
13 of 14 checks passed
@sacOO7 sacOO7 deleted the chore/path-based-liveobjects-serialization branch June 24, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants