2929 from ably .pubsub .objects .liveobject import LiveObjectUpdate
3030 from ably .pubsub .objects .realtimeobject import RealtimeObject
3131 from ably .pubsub .objects .subscription import Subscription
32- from ably .pubsub .objects .valuetypes import LiveMapValue , Primitive , T
32+ from ably .pubsub .objects .valuetypes import JsonObject , JsonValue , LiveMapValue , Primitive , T
3333
3434# The types `value(expected)` accepts as `expected`, and the category each one selects
3535_EXPECTED_VALUE_TYPES : tuple [tuple [type , ValueType ], ...] = (
@@ -103,36 +103,37 @@ def primitive_value(value: Any, value_type: ValueType | None = None) -> Any:
103103 return value
104104
105105
106- def compact_value (value : Any , * , for_json : bool = False ) -> Any :
107- """RTPO13, RTPO14: a plain snapshot of a resolved value.
106+ def compact_json_value (value : Any ) -> JsonValue :
107+ """RTPO14, RTINS11: a JSON-serializable snapshot of a resolved value, a map as
108+ `compact_json_map` builds it."""
109+ if isinstance (value , InternalLiveMap ):
110+ return compact_json_map (value )
111+ return _compact_json_leaf (value )
108112
109- A map becomes a dict of its entries that are not tombstoned (RTPO13c1), nested maps
110- becoming nested dicts (RTPO13c2) and counters their values (RTPO13c3). Maps are visited
111- depth first in entry order, and a map reached again in the same snapshot, through a
112- cycle or a second reference, is the dict already built for it (RTPO13c5). With
113- `for_json` it is `{'objectId': id}` instead (RTPO14b2), and binary is a base64 string
114- (RTPO14b1), so that the result is JSON-serializable.
115- """
116- if not isinstance (value , InternalLiveMap ):
117- return _compact_leaf (value , for_json )
118113
119- result : dict [str , Any ] = {}
120- built : dict [str , dict [str , Any ]] = {value .object_id : result }
114+ def compact_json_map (live_map : InternalLiveMap ) -> JsonObject :
115+ """RTPO13c, RTPO14b: a JSON object of a map's entries that are not tombstoned (RTPO13c1).
116+
117+ Nested maps become nested objects (RTPO13c2) and counters their values (RTPO13c3). Maps
118+ are visited depth first in entry order, and a map reached again in the same snapshot,
119+ through a cycle or a second reference, is `{'objectId': id}` (RTPO13c5, RTPO14b2).
120+ """
121+ result : JsonObject = {}
122+ visited = {live_map .object_id }
121123 # A stack in place of recursion, so that the depth of the tree is not limited by the
122- # interpreter's. Each item is a dict being built and the entries of its map still to add.
123- stack = [(result , iter (value .entries ()))]
124+ # interpreter's. Each item is an object being built and the entries of its map still to add.
125+ stack = [(result , iter (live_map .entries ()))]
124126 while stack :
125127 target , entries = stack [- 1 ]
126128 for key , entry_value in entries :
127129 if not isinstance (entry_value , InternalLiveMap ):
128- target [key ] = _compact_leaf (entry_value , for_json )
130+ target [key ] = _compact_json_leaf (entry_value )
129131 continue
130- seen = built .get (entry_value .object_id )
131- if seen is not None :
132- target [key ] = {'objectId' : entry_value .object_id } if for_json else seen
132+ if entry_value .object_id in visited :
133+ target [key ] = {'objectId' : entry_value .object_id }
133134 continue
134- child : dict [ str , Any ] = {}
135- built [ entry_value .object_id ] = child
135+ child : JsonObject = {}
136+ visited . add ( entry_value .object_id )
136137 target [key ] = child
137138 stack .append ((child , iter (entry_value .entries ())))
138139 break
@@ -141,11 +142,14 @@ def compact_value(value: Any, *, for_json: bool = False) -> Any:
141142 return result
142143
143144
144- def _compact_leaf (value : Any , for_json : bool ) -> Any :
145- """RTPO13c3, RTPO13c4, RTPO13d, RTPO13e, RTPO14b1: the snapshot of a value that is not a map."""
145+ def _compact_json_leaf (value : Any ) -> JsonValue :
146+ """RTPO13c3, RTPO13c4, RTPO13d, RTPO13e, RTPO14b1: the snapshot of a value that is not a map.
147+
148+ A counter is its value, binary a base64 string, and a JSON object or array a copy.
149+ """
146150 if isinstance (value , InternalLiveCounter ):
147151 return value .value ()
148- if for_json and isinstance (value , (bytes , bytearray )):
152+ if isinstance (value , (bytes , bytearray )):
149153 return base64 .b64encode (value ).decode ('ascii' )
150154 if isinstance (value , (dict , list )):
151155 return copy .deepcopy (value )
@@ -208,15 +212,11 @@ def get(self, key: str) -> Instance | None:
208212 return None
209213 return Instance ._wrap (self ._realtime_object , value ) # RTINS5c
210214
211- def compact (self ) -> Any :
212- """RTINS10: a plain snapshot of the wrapped value."""
213- self ._realtime_object ._check_access_preconditions () # RTINS10a
214- return compact_value (self ._value ) # RTINS10b
215-
216- def compact_json (self ) -> Any :
217- """RTINS11: `compact`, with binary as base64 and cycles as `{'objectId': ...}`. Never None."""
215+ def compact_json (self ) -> JsonValue :
216+ """RTINS11: a JSON-serializable snapshot of the wrapped value, as
217+ `PathObject.compact_json` builds it. Never None (RTINS11c)."""
218218 self ._realtime_object ._check_access_preconditions () # RTINS11a
219- return compact_value (self ._value , for_json = True ) # RTINS11b
219+ return compact_json_value (self ._value ) # RTINS11b
220220
221221 def as_live_map (self ) -> LiveMapInstance :
222222 """RTTS9a: this instance as a map. Raises AblyException 92007 if it wraps something else."""
@@ -266,6 +266,11 @@ def batch(self) -> Batch[LiveMapBatchContext]:
266266
267267 return Batch (self ._realtime_object , lambda : self ._value , LiveMapBatchContext , f'object { self .id !r} ' )
268268
269+ def compact_json (self ) -> JsonObject :
270+ """RTINS11, RTTS7a1: a JSON-serializable snapshot of the wrapped map."""
271+ self ._realtime_object ._check_access_preconditions () # RTINS11a
272+ return compact_json_map (self ._value ) # RTINS11b
273+
269274 def entries (self ) -> list [tuple [str , Instance ]]:
270275 """RTINS6: `(key, instance)` for each entry of the map.
271276
@@ -332,6 +337,11 @@ def value(self) -> float:
332337 self ._realtime_object ._check_access_preconditions () # RTINS4a
333338 return self ._value .value ()
334339
340+ def compact_json (self ) -> float :
341+ """RTINS11, RTTS7a3: the wrapped counter's value."""
342+ self ._realtime_object ._check_access_preconditions () # RTINS11a
343+ return self ._value .value () # RTINS11b
344+
335345 async def increment (self , amount : float = 1 ) -> None :
336346 """RTINS14: increments the wrapped counter by `amount`."""
337347 self ._realtime_object ._check_write_preconditions () # RTINS14b
0 commit comments