Source code for aiowamp.serializers.json

import base64
import json
from typing import Any, ByteString, Iterable, Optional, Tuple

import aiowamp
from aiowamp import SerializerABC, build_message_from_list

__all__ = ["JSONSerializer", "JSONDecoder", "JSONEncoder"]


[docs]class JSONSerializer(SerializerABC): """Serializer for the JSON format. Provides a custom `json.JSONDecoder` and `json.JSONEncoder` which handle the special WAMP string format for binary data. """ __slots__ = ("decoder", "encoder") decoder: json.JSONDecoder """JSON decoder used to decode incoming messages.""" encoder: json.JSONEncoder """JSON encoder used to encode outgoing messages."""
[docs] def __init__(self, *, decoder: json.JSONDecoder = None, encoder: json.JSONEncoder = None) -> None: """ Args: decoder: Decoder to be used. Defaults to `JSONDecoder` which supports binary data in strings. encoder: Encoder to be used. Defaults to `JSONEncoder` which supports binary data in strings. """ self.decoder = decoder or JSONDecoder() self.encoder = encoder or JSONEncoder(check_circular=False)
[docs] def serialize(self, msg: aiowamp.MessageABC) -> bytes: return self.encoder.encode(msg.to_message_list()).encode()
[docs] def deserialize(self, data: bytes) -> aiowamp.MessageABC: msg_list = self.decoder.decode(data.decode()) return build_message_from_list(msg_list)
def is_encoded_bytes(s: str) -> bool: """Check if the given string contains encoded binary data. Args: s: String to check. Returns: Whether the given string holds encoded binary data. """ return s.startswith("\0") def encode_bytes(b: ByteString) -> str: """Encode the binary data to a string. Args: b: Binary data to encode. Returns: WAMP JSON string representation of the binary data. """ e = b"\0" + base64.b64encode(b) return e.decode() def decode_bytes(s: str) -> bytes: """Decode the bytes. Args: s: Encoded binary content. Returns: Decoded binary data. Raises: binascii.Error: If the data isn't valid. """ return base64.b64decode(s[1:]) def _get_item_iter(v: Any) -> Optional[Iterable[Tuple[Any, Any]]]: """Get a key-value iterable for the given object. Args: v: Any JSON object. Returns: An iterable which yields 2-tuples where the first element is the index value and the second element is the value. `None`, if the given object isn't a container. """ if isinstance(v, list): return enumerate(v) if isinstance(v, dict): return v.items() return None def decode_bytes_in_json_obj(v: Any) -> Any: """Decode nested bytes in the given object. If the given object is a container type it WILL BE MUTATED DIRECTLY. Args: v: Any JSON object. Returns: Same object with binary data decoded. """ if isinstance(v, str): if is_encoded_bytes(v): return decode_bytes(v) return v item_iter = _get_item_iter(v) if not item_iter: return v stack = [(v, item_iter)] while stack: container, item_iter = stack.pop() for key, value in item_iter: if isinstance(value, str): if is_encoded_bytes(value): container[key] = decode_bytes(value) continue sub_item_iter = _get_item_iter(value) if sub_item_iter: stack.append((value, sub_item_iter)) return v class JSONDecoder(json.JSONDecoder): """JSONDecoder with support for binary data.""" __slots__ = () def raw_decode(self, s, idx=0) -> Tuple[Any, int]: decoded, end = super().raw_decode(s, idx) return decode_bytes_in_json_obj(decoded), end class JSONEncoder(json.JSONEncoder): """JSONEncoder with support for binary data. Treats all `ByteString` types as binary data. """ __slots__ = () def default(self, o: Any) -> Any: if isinstance(o, ByteString): return encode_bytes(o) return super().default(o)