# Copyright 2026 jCloud # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy from typing import * import collections.abc __all__ = [ 'serialize' ] _ESCAPE_SEQUENCES = { '\"': r'\"', '\\': r'\\', '\b': r'\b', '\f': r'\f', '\n': r'\n', '\r': r'\r', '\t': r'\t', } class _Mapper: def __init__(self, mapping = {}): self._mapping = copy.deepcopy(mapping) def _set(self, key, value): self._mapping[key] = value def set(self, key, value): self._set(key, value) return self def map(self, value): return self._mapping[value] _MAPPER = _Mapper().set(True, 'true').set(False, 'false').set(None, 'null') def _serialize_object(data: dict, level: int, indent: int, indent_char: str, separators: Tuple[str, str]) -> str: result = '{' _count = 0 for _key, value in data.items(): _count += 1 key = _serialize(_key, 0, 0, '', separators) value = _serialize(value, level + 1, indent, indent_char, separators) if not isinstance(_key, str): key = '"' + key + '"' if indent: result += '\n' result += (level + 1) * indent * indent_char result += key result += separators[1] result += value if _count != len(data.keys()): if indent: result += separators[0].strip() else: result += separators[0] if indent: result += '\n' result += level * indent * indent_char + '}' return result def _serialize_array(data: Sequence, level: int, indent: int, indent_char: str, separators: Tuple[str, str]) -> str: result = '[' _count = 0 for value in data: _count += 1 value = _serialize(value, level + 1, indent, indent_char, separators) if indent: result += '\n' result += (level + 1) * indent * indent_char result += value if _count != len(data): if indent: result += separators[0].strip() else: result += separators[0] if indent: result += '\n' result += level * indent * indent_char + ']' return result def _serialize_string(data: str) -> str: _data = data data = '' for c in _data: data += _ESCAPE_SEQUENCES.get(c, c) return '"' + data + '"' def _serialize_number(data: int | float) -> str: return str(data) def _serialize(data: dict | list | str | int | float | bool | None, level: int, indent: int, indent_char: str, separators: Tuple[str, str]) -> str: if isinstance(data, (bool, type(None))): return _MAPPER.map(data) elif isinstance(data, dict): return _serialize_object(data, level, indent, indent_char, separators) elif isinstance(data, str): return _serialize_string(data) elif isinstance(data, (int, float)): return _serialize_number(data) elif isinstance(data, collections.abc.Sequence): return _serialize_array(data, level, indent, indent_char, separators) else: raise TypeError(f'Object of type {type(data).__name__} is not JSON serializable') def _char_whitespaces(whitespaces: Tuple[int | str, int | str] | int | str) -> Tuple[str, str]: if isinstance(whitespaces, collections.abc.Sequence) and not isinstance(whitespaces, str): if len(whitespaces) != 2: raise ValueError(f'expected exactly two elements in whitespaces, got {len(whitespaces)}') if isinstance(whitespaces, (str, int)): whitespaces = whitespaces, whitespaces if isinstance(whitespaces[0], int): whitespaces = whitespaces[0] * ' ', whitespaces[1] if isinstance(whitespaces[1], int): whitespaces = whitespaces[0], whitespaces[1] * ' ' return whitespaces def serialize(data: dict | list | str | int | float | bool | None, indent: int = 0, indent_char: str = ' ', separators: Tuple[str, str] = (', ', ': ')) -> str: ''' Serializes the data to a JSON formatted string. :param data: The data. :type data: dict | list | str | int | float | bool | None :param indent: The indentation :type indent: int :param indent_char: The character the indentation will be filled with :type indent_char: str :param separators: A tuple with the separators. The first string is the element separator and the second string is the key value separator. :type separators: tuple :raises TypeError: If the type is not JSON serializable. :return: The JSON formatted string. :rtype: str ''' if len(separators) != 2: raise ValueError(f'expected exactly two elements in separators, got {len(separators)}.') return _serialize(data, 0, indent, indent_char, separators)