8

The following code is giving me:

Runtime.MarshalError: Unable to marshal response: {'Yes'} is not JSON serializable

from calendar import monthrange

def time_remaining_less_than_fourteen(year, month, day):
    a_year = int(input['year'])
    b_month = int(input['month'])
    c_day = int(input['day'])
    days_in_month = monthrange(int(a_year), int(b_month))[1]
    time_remaining = ""

    if (days_in_month - c_day) < 14:
        time_remaining = "No"
        return time_remaining

    else:
        time_remaining = "Yes"
        return time_remaining


output = {time_remaining_less_than_fourteen((input['year']), (input['month']), (input['day']))}

#print(output)

When I remove {...} it then throws: 'unicode' object has no attribute 'copy'

xavdid
  • 5,092
  • 3
  • 20
  • 32
Samuel Hart
  • 81
  • 1
  • 1
  • 2

2 Answers2

27

I encountered this issue when working with lambda transformation blueprint kinesis-firehose-process-record-python for Kinesis Firehose which led me here. Thus I will post a solution to anyone who also finds this questions when having issues with the lambda.

The blueprint is:

from __future__ import print_function

import base64

print('Loading function')


def lambda_handler(event, context):
    output = []

    for record in event['records']:
        print(record['recordId'])
        payload = base64.b64decode(record['data'])

        # Do custom processing on the payload here

        output_record = {
            'recordId': record['recordId'],
            'result': 'Ok',
            'data': base64.b64encode(payload)
        }
        output.append(output_record)

    print('Successfully processed {} records.'.format(len(event['records'])))

    return {'records': output}

The thing to note is that the Firehose lambda blueprints for python provided by AWS are for Python 2.7, and they don't work with Python 3. The reason is that in Python 3, strings and byte arrays are different.

The key change to make it work with lambda powered by Python 3.x runtime was:

changing

'data': base64.b64encode(payload)

into

'data': base64.b64encode(payload).decode("utf-8")

Otherwise, the lambda had an error due to inability to serialize JSON with byte array returned from base64.b64encode.

Marcin
  • 215,873
  • 14
  • 235
  • 294
  • 2
    Thanks Marcin, I was having this exact same issue and your answer just saved me a lot of headaches. That Python 2.7 blueprint led me to believe that Kinesis was expecting `bytes` as the record's data. Conversion to `str` didn't even cross my mind. Quick suggestion: `base64.b64encode(payload).decode("utf-8")` could be simply `base64.b64encode(payload).decode()`. Base64 data is, by definition, ASCII. ASCII itself is UTF-8, which is the default value for the encoding parameter in Python 3. Specifying the codec there is just redundant. – Rafa Viotti Mar 19 '20 at 02:14
  • 2
    @Rafa Glad my answer helped, and thanks for the good suggestion. – Marcin Mar 19 '20 at 02:17
  • 2
    Not sure whether you saved my life literally or merely figuratively, but thanks for this answer either way.^^ +1 – Flo Jul 07 '20 at 16:53
  • 1
    Thanks for saving my time @Marcin. Had the same issue and your solution worked. – VinayBS May 30 '21 at 18:46
3

David here, from the Zapier Platform team.

Per the docs:

output: A dictionary or list of dictionaries that will be the "return value" of this code. You can explicitly return early if you like. This must be JSON serializable!

In your case, output is a set:

>>> output = {'Yes'}
>>> type(output)
<class 'set'>
>>> json.dumps(output)
Object of type set is not JSON serializable

To be serializable, you need a dict (which has keys and values). Change your last line to include a key and it'll work like you expect:

#         \ here /
output = {'result': time_remaining_less_than_fourteen((input['year']), (input['month']), (input['day']))}
xavdid
  • 5,092
  • 3
  • 20
  • 32