47

I have some objects I want to send to celery tasks on my application. Those objects are obviously not json serializable using the default json library. Is there a way to make celery serialize/de-serialize those objects with custom JSON Encoder/Decoder?

Rafael S. Calsaverini
  • 13,582
  • 19
  • 75
  • 132

1 Answers1

73

A bit late here, but you should be able to define a custom encoder and decoder by registering them in the kombu serializer registry, as in the docs: http://docs.celeryproject.org/en/latest/userguide/calling.html#serializers.

For example, the following is a custom datetime serializer/deserializer (subclassing python's builtin json module) for Django:


myjson.py (put it in the same folder of your settings.py file)

import json
from datetime import datetime
from time import mktime

class MyEncoder(json.JSONEncoder):   
    def default(self, obj):
        if isinstance(obj, datetime):
            return {
                '__type__': '__datetime__', 
                'epoch': int(mktime(obj.timetuple()))
            }
        else:
            return json.JSONEncoder.default(self, obj)

def my_decoder(obj):
    if '__type__' in obj:
        if obj['__type__'] == '__datetime__':
            return datetime.fromtimestamp(obj['epoch'])
    return obj

# Encoder function      
def my_dumps(obj):
    return json.dumps(obj, cls=MyEncoder)

# Decoder function
def my_loads(obj):
    return json.loads(obj, object_hook=my_decoder)


settings.py

# Register your new serializer methods into kombu
from kombu.serialization import register
from .myjson import my_dumps, my_loads

register('myjson', my_dumps, my_loads, 
    content_type='application/x-myjson',
    content_encoding='utf-8') 

# Tell celery to use your new serializer:
CELERY_ACCEPT_CONTENT = ['myjson']
CELERY_TASK_SERIALIZER = 'myjson'
CELERY_RESULT_SERIALIZER = 'myjson'
tbodt
  • 16,609
  • 6
  • 58
  • 83
f.cipriani
  • 3,357
  • 2
  • 26
  • 22
  • Where should we put this code? Where do we import it? – Eduard Luca Sep 08 '14 at 08:38
  • @EduardLuca are you using Django? Please see my edit – f.cipriani Sep 08 '14 at 09:56
  • Cool. I had to pull out the `register` call from `settings.py` (for some reason it threw a totally non-related error), but that worked. Thanks! – Eduard Luca Sep 08 '14 at 11:17
  • 1
    There is a typo: 'epoc' should be 'epoch' on lines 10 and 20 – xxx Oct 16 '14 at 13:10
  • It will be nice to check `Promise` instances as well, i.e. gettext_lazy objects in `MyEncoder.default` method. `from django.utils.functional import Promise` – Alexandr Bulanov Feb 17 '16 at 12:52
  • I ended up having to do: ```def my_loads(obj): return json.loads(six.text_type(obj), object_hook=my_decoder)``` otherwise, it complains about getting a buffer instead of a string. – LarrikJ Aug 30 '17 at 17:39
  • How to add custom serializer for sending and receiving numpy arrays? – The Gr8 Adakron Jul 27 '18 at 05:41
  • @TheGr8Adakron you can use the same pattern with methods available in another library. Exercise for the reader :) – floer32 Sep 13 '18 at 19:03
  • Another note, in the Django case, another spot where most of this could live instead of a `settings.py` file is in the `celery_app.py` before `Celery` is initialized. (Of course, the `CELERY_*` settings will remain in the settings file, but the rest can move if that seems like tidier organization for your project.) – floer32 Sep 13 '18 at 19:04
  • 3
    This used to work perfectly fine, but since celery 4.2 I cannot get it to work. Error when starting celery worker: Unrecoverable error: ContentDisallowed('Refusing to deserialize untrusted content of type json (application/json)',) – gabn88 Sep 21 '18 at 18:27
  • 1
    Kombu `utils.json.py` now provides, [`register_type`](https://github.com/celery/kombu/blob/35f24f1f01c7895926a990be0616a4582374749a/kombu/utils/json.py#L95). Pretty straightforward. See [here](https://docs.celeryq.dev/projects/kombu/en/latest/userguide/serialization.html#:~:text=If%20you%20need%20support%20for%20custom%20types%2C%20you%20can%20write%20serialize/deserialize%20functions%20and%20register%20them%20as%20follows%3A) for usage. – trozzel Mar 08 '23 at 16:32
  • @gabn88 I encountered the same, this is because Celery's heartbeat uses the default json type. So the solution is to still accept that: `CELERY_ACCEPT_CONTENT = ['json', 'myjson']` – MarkM Jun 02 '23 at 10:05