Boto3でLambdaを呼び出すと”Unable to marshal response: Object of type StreamingBody is not JSON serializable”エラーとなる

2020.05.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部の若槻です。

今回は、Boto3でLambdaを呼び出した時の戻り値の処理がUnable to marshal response: Object of type StreamingBody is not JSON serializableエラーとなったので対処した話です。

事象

以下のように実行される側(scan_func)と実行する側(execute_func)のLambdaがあり、後者から前者を呼び出してscan_funcからの戻り値を得たいです。

  • 実行される側(scan_func
import json
import boto3

def lambda_handler(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('wakatsuki_test')
    resp = table.scan()

    return json.dumps(resp['Items'])
  • 実行する側(invoke_func
import json
import boto3

def lambda_handler(event, context):
    resp = boto3.client('lambda').invoke(
        FunctionName='scan_func',
        InvocationType='RequestResponse'
    )
    return resp

しかしinvoke_funcを実行したところ、下記のUnable to marshal response: Object of type StreamingBody is not JSON serializableエラーが発生してしまいます。

Response:
{
  "errorMessage": "Unable to marshal response: Object of type StreamingBody is not JSON serializable",
  "errorType": "Runtime.MarshalError"
}

調査

Boto3のドキュメントを確認してみます。

ドキュメントによると、boto3.client('lambda').invoke()のレスポンスは以下のような形式を取るとのことです。

{
    'StatusCode': 123,
    'FunctionError': 'string',
    'LogResult': 'string',
    'Payload': StreamingBody(),
    'ExecutedVersion': 'string'
}

このうち、関数からのレスポンスはPayload (StreamingBody)に含まれるとのことです。今回エラーとなっているのはこの部分のJSONシリアライズです。

・ Payload (StreamingBody) -- The response from the function, or an error object.

ではこのStreamingBodyはどんなデータ型であるかというと、botocoreのドキュメントに記載がありました。

class botocore.response.StreamingBody(raw_stream, content_length)

Wrapper class for an http response body.

どうやらread()メソッドを使えばStreamingBodyに含まれるレスポンスボディを取得できそうです。

read(amt=None)

Read at most amt bytes from the stream. If the amt argument is omitted, read all data.

解決

調査を踏まえて、Lambdaを呼び出したレスポンスをresp['Payload'].read()のようにパースすることにより、scan_funcからの戻り値を得ることができるようになりました。

import json
import boto3

def lambda_handler(event, context):
    resp = boto3.client('lambda').invoke(
        FunctionName='test_wakatsuki',
        InvocationType='RequestResponse'
    )
    return resp['Payload'].read()
Response:
"[{\"id\": \"aaa\"}]"

おわりに

あまり使ったことがないメソッドやデータを扱うときは、今回のようにまずデータの型を調査する癖をつけたいですね。

以上