Манкипатчим поддержку decimal.Decimal в json

TypeError: Decimal('0.99') is not JSON serializable

Вот, что заставляет людей гуглить, как добавить поддержку новых типов в стандартную либу json. Вообще, пост не об этом, вот просто парочка правильных решений:

  1. использовать simplejson
  2. создать свой энкодер, пронаследовавшись от стандартного, и передавать его в json.dumps в качестве одного из аргументов.
    Код

В общем, все здорово, но что делать с вызовами из всяких third-party либ и с тучей вызовов по всему проекту? Короче, пост про манкипатчинг 🙂

Disclaimer

Monkey-patch — это грязный прием, в продакшене его использовать можно, если сильно хочется нельзя. В общем, как обычно, все на ваш страх и риск.

Итак, идея в том, чтобы подменить метод у стандартного класса JSONEncoder, что возымеет страшный сайд-эффект- автоматически все вызовы этого метода откуда бы то ни было (даже на закешированном инстансе этого энкодера) будут приводить в наш хук — но ведь это нам и нужно, да?

from json import JSONEncoder
from decimal import Decimal
import types

# сохраняем ссылку на настоящую функцию
real_default = JSONEncoder.default.__func__
def default(self, obj):
    if isinstance(obj, Decimal):
        return float(obj) 
    # вызываем стандартный обработчик для всех неинтересных нам случаев
    return real_default(self, obj)
#собственно, манкипатчим метод
JSONEncoder.default = types.MethodType(default, None, JSONEncoder)

 

Все, теперь простой json.dumps поддерживает Decimal.

Спасибо, что читаете!

Запись опубликована в рубрике На заметку, Решение проблем с метками , . Добавьте в закладки постоянную ссылку.

Один комментарий: Манкипатчим поддержку decimal.Decimal в json

  1. apatrushev говорит:

    Я бы ещё functools.wraps добавил. Для чистоты.
    Или вообще декоратор сделал, чтобы спрятать глобальную переменную в контекст. Например, так:

    import functools
    from decimal import Decimal
    from json import JSONEncoder

    def wrap_default(real_default):
    @functools.wraps(real_default)
    def wrapper(self, obj):
    if isinstance(obj, Decimal):
    return float(obj)
    return real_default(self, obj)
    return wrapper

    JSONEncoder.default = wrap_default(JSONEncoder.default)

    http://pastebin.com/JTHfE4u8

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *