Можно ли переопределить сериализатор JSONEncoder для стандартных типов?

Можно ли как-нибудь переопределить поведение JSONEncoder для стандартных типов? Например, хочу, чтобы словарь сериализовывался не через {}, а через <>, чтобы строки не были обёрнуты в " и т.д. Причём, как-то особенно обрабатывать необходимо только некоторые из стандартных типов


Например, код:

import json

print(json.dumps({"a": "5", "b": {"c": "d", "e": "f", "g": ["1", "2", 3]}}, indent=4))

Вывод:

{
    "a": "5",
    "b": {
        "c": "d",
        "e": "f",
        "g": [
            "1",
            "2",
            3
        ]
    }
}

Я же хочу получить:

<
    a: 5,
    b: <
        c: d,
        e: f,
        g: [
            1,
            2,
            3
        ]
    >
>

Необходимо получить данные именно в "кастомном" виде, десериализация не нужна.


Находил, как можно добавить сериализацию нестандартных типов, но как поменять поведение именно для стандартных типов (dict, list, str, int, float, True, False, None) не нашёл.


Ответы (1 шт):

Автор решения: Михаил Муругов

Разобрался. Переопределять надо было методы encode и iterencode класса JSONEncoder.


Мой, очень костыльный вариант:

import json


class CustomEncoder(json.JSONEncoder):  # Определяем собственный кодировщик
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.current_level = 0  # Текущий уровень отступа

    def encode(self, o):  # Как я понял, правило кодирования типов, кроме list и dict
        if isinstance(o, str):  # Наше правило кодирования строки
            return o  # Не оборачиваем в "
        else:  # Иначе кодируем правилом по умолчанию
            return super().encode(o)

    def iterencode(self, o, _one_shot=False):  # Правило кодирования list и dict
        if isinstance(o, dict):  # Наше правило кодирования словаря
            yield "<\n"  # Начинаем сериализацию словаря с <
            self.current_level += 1  # Увеличиваем текущий уровень отступа
            for key, val in o.items():
                yield "{indent}{key}{key_separator}{value}{item_separator}\n".format(
                    indent=" " * self.indent * self.current_level,  # Вычисляем отступ
                    key=self.encode(key),  # Кодируем ключ словаря по нашему правилу
                    key_separator=self.key_separator,
                    value=self.encode(val),  # Кодируем значение по ключу по нашему правилу
                    item_separator=self.item_separator
                )
            self.current_level -= 1
            yield "{indent}>".format(  # Делаем отступ и
                indent=" " * self.indent * self.current_level. # Закрываем словарь символом >
            )
        else:
            yield from super().iterencode(o)


mydict = {
    "a": "5",
    "b": {
        "c": "d",
        "e": "f",
        "g": {
            "1": "one",
            "2": "two",
        }
    }
}
print(json.dumps(
    obj=mydict,  # Что сериализуем
    cls=CustomEncoder,  # Кастомный кодировщик
    indent=4,  # Количество пробелов в отступе
))


Вывод:

<
    a: 5,
    b: <
        c: d,
        e: f,
        g: <
            1: one,
            2: two,
        >,
    >,
>


Для моей задачи работает очень даже хорошо, но будет беда с отступами, если в сериализуемом объекте где-нибудь будет список.

→ Ссылка