Home > Mobile >  Can I make json.dumps/loads work with Encoder/Decoder instances instead of classes?
Can I make json.dumps/loads work with Encoder/Decoder instances instead of classes?

Time:06-03

Please consider this toy example of a custom JSON-encoder.

import json
from typing import Any

class MyNumber:
    def __init__(self, num):
        self.num = num

class MultEncoder(json.JSONEncoder):
    'Multiply MyNumber instances when JSON-encoding.'

    def default(self, o: Any) -> Any:
        if isinstance(o, MyNumber):
            return o.num*2
        return super().default(o)

It works like this:

>>> json.dumps({"a": MyNumber(5)}, cls=MultEncoder)
'{"a": 10}'

My question is how can I make the factor dynamic? I would like to do:

class MultEncoder(json.JSONEncoder):
    'Multiply MyNumber instances when JSON-encoding.'
    
    def __init__(self, factor, *args, **kwargs):
        self.factor = factor        
        super().__init__(*args, **kwargs)

    def default(self, o: Any) -> Any:
        if isinstance(o, MyNumber):
            return o.num*self.factor
        return super().default(o)

But of course, json.dumps({"a": MyNumber(5)}, cls=MultEncoder(7)) fails with

TypeError: 'MultEncoder' object is not callable

because the cls argument is expected to be a class, not an instance.

edit1: Note that I cannot write custom my_json_dumps/my_json_loads functions, I can only control which encoder/decoder classes get imported in other parts of the code where json.dumps and loads are used.

edit2: Note that I tried to make a general, simple example. In my real code the encoder/decoder need to know credentials for connecting to a database and other dynamic configuration.

CodePudding user response:

I came up with a pretty ugly solution: a class-factory function where the returned class accesses closure-variables.

def MultEncoder(factor):
    class _MultEncoder(json.JSONEncoder):
        'Multiply MyNumber instances when JSON-encoding.'

        def default(self, o: Any) -> Any:
            if isinstance(o, MyNumber):
                return o.num*factor
            return super().default(o)

    return _MultEncoder

Demo:

>>> json.dumps({"a": MyNumber(5)}, cls=MultEncoder(7))
'{"a": 35}'

Is this the best we can do here?

CodePudding user response:

Make MultEncoder class instances callable by simply adding the __call__ method.

class MyNumber:
    def __init__(self, num):
        self.num = num
class MultEncoder(json.JSONEncoder):
    'Multiply MyNumber instances when JSON-encoding.'
    
    def __init__(self, factor, *args, **kwargs):
        self.factor = factor        
        super().__init__(*args, **kwargs)
    def __call__(self,**kwargs):
        return self

    def default(self, o: Any) -> Any:
        if isinstance(o, MyNumber):
            return o.num*self.factor
        return super().default(o)
json.dumps({"a": MyNumber(5)}, cls=MultEncoder(7))

And the output is:

'{"a": 35}'
  • Related