@@ -55,7 +55,80 @@ def __init__(self, *args, **kwargs):
5555 self ._simple_value = args [0 ]._simple_value
5656
5757 if not self ._is_null :
58- super (JsonObject , self ).__init__ (* args , ** kwargs )
58+ super ().__init__ (* args , ** kwargs )
59+
60+ def __len__ (self ):
61+ if self ._is_null :
62+ return 0
63+ if self ._is_array :
64+ return len (self ._array_value )
65+ if self ._is_scalar_value :
66+ return 1
67+ return super ().__len__ ()
68+
69+ def __bool__ (self ):
70+ if self ._is_null :
71+ return False
72+ if self ._is_array :
73+ return bool (self ._array_value )
74+ if self ._is_scalar_value :
75+ return True
76+ return super ().__len__ () > 0
77+
78+ def __iter__ (self ):
79+ if self ._is_array :
80+ return iter (self ._array_value )
81+ if self ._is_scalar_value :
82+ raise TypeError (
83+ f"'{ type (self ._simple_value ).__name__ } ' object is not iterable"
84+ )
85+ return super ().__iter__ ()
86+
87+ def __getitem__ (self , key ):
88+ if self ._is_array :
89+ return self ._array_value [key ]
90+ if self ._is_scalar_value :
91+ raise TypeError (
92+ f"'{ type (self ._simple_value ).__name__ } ' object is not subscriptable"
93+ )
94+ return super ().__getitem__ (key )
95+
96+ def __contains__ (self , item ):
97+ if self ._is_array :
98+ return item in self ._array_value
99+ if self ._is_scalar_value :
100+ raise TypeError (
101+ f"argument of type '{ type (self ._simple_value ).__name__ } ' "
102+ "is not iterable"
103+ )
104+ return super ().__contains__ (item )
105+
106+ def __eq__ (self , other ):
107+ if isinstance (other , JsonObject ):
108+ if self ._is_array :
109+ return (
110+ getattr (other , "_is_array" , False )
111+ and self ._array_value == other ._array_value
112+ )
113+ if self ._is_scalar_value :
114+ return (
115+ getattr (other , "_is_scalar_value" , False )
116+ and self ._simple_value == other ._simple_value
117+ )
118+ if self ._is_null :
119+ return getattr (other , "_is_null" , False )
120+ return not (
121+ getattr (other , "_is_array" , False )
122+ or getattr (other , "_is_scalar_value" , False )
123+ or getattr (other , "_is_null" , False )
124+ ) and super ().__eq__ (other )
125+ if self ._is_array :
126+ return self ._array_value == other
127+ if self ._is_scalar_value :
128+ return self ._simple_value == other
129+ if self ._is_null :
130+ return other is None or (isinstance (other , dict ) and len (other ) == 0 )
131+ return super ().__eq__ (other )
59132
60133 def __repr__ (self ):
61134 if self ._is_array :
@@ -64,7 +137,7 @@ def __repr__(self):
64137 if self ._is_scalar_value :
65138 return str (self ._simple_value )
66139
67- return super (JsonObject , self ).__repr__ ()
140+ return super ().__repr__ ()
68141
69142 @classmethod
70143 def from_str (cls , str_repr ):
@@ -90,17 +163,33 @@ def serialize(self):
90163 if self ._is_null :
91164 return None
92165
166+ raw = _unwrap_for_json (self )
93167 if self ._is_scalar_value :
94- return json .dumps (self ._simple_value )
168+ return json .dumps (raw )
169+
170+ return json .dumps (raw , sort_keys = True , separators = ("," , ":" ))
95171
96- if self ._is_array :
97- return json .dumps (self ._array_value , sort_keys = True , separators = ("," , ":" ))
98172
99- return json .dumps (self , sort_keys = True , separators = ("," , ":" ))
173+ def _unwrap_for_json (val ):
174+ """Recursively unwrap JsonObject instances for safe json.dumps serialization."""
175+ if isinstance (val , JsonObject ):
176+ if val ._is_null :
177+ return None
178+ if val ._is_array :
179+ return [_unwrap_for_json (item ) for item in val ._array_value ]
180+ if val ._is_scalar_value :
181+ return val ._simple_value
182+ return {k : _unwrap_for_json (v ) for k , v in val .items ()}
183+ if isinstance (val , dict ):
184+ return {k : _unwrap_for_json (v ) for k , v in val .items ()}
185+ if isinstance (val , (list , tuple )):
186+ return [_unwrap_for_json (item ) for item in val ]
187+ return val
100188
101189
102190_INTERVAL_PATTERN = re .compile (
103- r"^P(-?\d+Y)?(-?\d+M)?(-?\d+D)?(T(-?\d+H)?(-?\d+M)?(-?((\d+([.,]\d{1,9})?)|([.,]\d{1,9}))S)?)?$"
191+ r"^P(-?\d+Y)?(-?\d+M)?(-?\d+D)?"
192+ r"(T(-?\d+H)?(-?\d+M)?(-?((\d+([.,]\d{1,9})?)|([.,]\d{1,9}))S)?)?$"
104193)
105194
106195
@@ -109,7 +198,7 @@ class Interval:
109198 """Represents a Spanner INTERVAL type.
110199
111200 An interval is a combination of months, days and nanoseconds.
112- Internally, Spanner supports Interval value with the following range of individual fields:
201+ Internally, Spanner supports Interval value with individual fields range :
113202 months: [-120000, 120000]
114203 days: [-3660000, 3660000]
115204 nanoseconds: [-316224000000000000000, 316224000000000000000]
@@ -199,12 +288,14 @@ def from_str(cls, s: str) -> "Interval":
199288 parts = match .groups ()
200289 if not any (parts [:3 ]) and not parts [3 ]:
201290 raise ValueError (
202- f"Invalid interval format: at least one component (Y/M/D/H/M/S) is required: { s } "
291+ "Invalid interval format: at least one component "
292+ f"(Y/M/D/H/M/S) is required: { s } "
203293 )
204294
205295 if parts [3 ] == "T" and not any (parts [4 :7 ]):
206296 raise ValueError (
207- f"Invalid interval format: time designator 'T' present but no time components specified: { s } "
297+ "Invalid interval format: time designator 'T' present "
298+ f"but no time components specified: { s } "
208299 )
209300
210301 def parse_num (s : str , suffix : str ) -> int :
@@ -298,14 +389,14 @@ def _proto_enum(int_val, proto_enum_object):
298389
299390
300391def get_proto_message (bytes_string , proto_message_object ):
301- """parses serialized protocol buffer bytes' data or its list into proto message or list of proto message.
392+ """Parses serialized protocol buffer bytes data or list into proto message.
302393
303394 Args:
304395 bytes_string (bytes or list[bytes]): bytes object.
305396 proto_message_object (Message): Message object for parsing
306397
307398 Returns:
308- Message or list[Message]: parses serialized protocol buffer data into this message.
399+ Message or list[Message]: Parsed protocol buffer message(s) .
309400
310401 Raises:
311402 ValueError: if the input proto_message_object is not of type Message
0 commit comments