Skip to content

Add support for serializing Enum values automatically in API Gateway Event Handler #519

Closed
@marcioemiranda

Description

@marcioemiranda

If a route function returns a dict containing an Enum, the json encoder will fail.

Expected Behavior

Since the Response was not returned, API Gateway event handler will create one. It calls json.dumps to serialize the dict using its own encoder.
The expected json output would transform the Enum in its value.

Current Behavior

The current behavior is that the encoder throws a TypeError saying Enum is not serializable.

Possible Solution

My suggestion is to check if the object is an Enum and output its value.
The example bellow handle Enum and also a set

class CustomEncoder(JSONEncoder):
    def default(self, o):
        if isinstance(o, Enum):
            return o.value
        try:
            iterable = iter(o)
        except TypeError:
            pass
        else:
            return list(iterable)    
        # Let the base class default method raise the TypeError    
        return JSONEncoder.default(self, o)

For now the workaround is to always create a Response and call json.dumps with my own custom encoder

Steps to Reproduce (for bugs)

Try the following code

from enum import Enum

class Color(Enum): 
   RED =1
   BLUE = 2

@app.get("/colors")
def get_color() -> Dict:
   return {
      "color": Color.RED,
      "variations": {"light", "dark"} 
   }

Environment

Lambda Power Tools version 1.17.1
Running with PyTest

../../../../Library/Caches/pypoetry/virtualenvs/te-admin-api-y-cvF00h-py3.8/lib/python3.8/site-packages/aws_lambda_powertools/tracing/tracer.py:313: in decorate
    response = lambda_handler(event, context, **kwargs)
_admin_controller.py:273: in handler
    return app.resolve(event, context)
../../../../Library/Caches/pypoetry/virtualenvs/te-admin-api-y-cvF00h-py3.8/lib/python3.8/site-packages/aws_lambda_powertools/event_handler/api_gateway.py:418: in resolve
    return self._resolve().build(self.current_event, self._cors)
../../../../Library/Caches/pypoetry/virtualenvs/te-admin-api-y-cvF00h-py3.8/lib/python3.8/site-packages/aws_lambda_powertools/event_handler/api_gateway.py:450: in _resolve
    return self._call_route(route, match.groupdict())
../../../../Library/Caches/pypoetry/virtualenvs/te-admin-api-y-cvF00h-py3.8/lib/python3.8/site-packages/aws_lambda_powertools/event_handler/api_gateway.py:478: in _call_route
    return ResponseBuilder(self._to_response(route.func(**args)), route)
../../../../Library/Caches/pypoetry/virtualenvs/te-admin-api-y-cvF00h-py3.8/lib/python3.8/site-packages/aws_lambda_powertools/event_handler/api_gateway.py:497: in _to_response
    body=json.dumps(result, separators=(",", ":"), cls=Encoder),
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/__init__.py:234: in dumps
    return cls(
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py:199: in encode
    chunks = self.iterencode(o, _one_shot=True)
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py:257: in iterencode
    return _iterencode(o, 0)
../../../../Library/Caches/pypoetry/virtualenvs/te-admin-api-y-cvF00h-py3.8/lib/python3.8/site-packages/aws_lambda_powertools/shared/json_encoder.py:16: in default
    return super().default(obj)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <aws_lambda_powertools.shared.json_encoder.Encoder object at 0x7f915fbaea90>
o = <Region.CENTRAL: 6>

    def default(self, o):
        """Implement this method in a subclass such that it returns
        a serializable object for ``o``, or calls the base implementation
        (to raise a ``TypeError``).
    
        For example, to support arbitrary iterators, you could
        implement default like this::
    
            def default(self, o):
                try:
                    iterable = iter(o)
                except TypeError:
                    pass
                else:
                    return list(iterable)
                # Let the base class default method raise the TypeError
                return JSONEncoder.default(self, o)
    
        """
>       raise TypeError(f'Object of type {o.__class__.__name__} '
                        f'is not JSON serializable')
E       TypeError: Object of type Region is not JSON serializable

/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/encoder.py:179: TypeError

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions