152 lines
5.2 KiB
Python
152 lines
5.2 KiB
Python
# 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) |