Lambda関数にてUnable to marshal responseが発生した場合の対処法

記事タイトルとURLをコピーする

こんにちは、AS部DS2課の外崎です。

Lambda 関数の開発中に「Unable to marshal response: Object of type HTTPResponse is not JSON serializable」というエラーに遭遇したことはありませんか?

このエラーは、レスポンスのオブジェクトが JSON シリアライズできないために発生します。場合によってはハンドラー関数が複数回実行するなどのバグが発生することがあります。(私自身がそうでした。)

本記事では、このエラーの原因と解決方法について詳しく解説します。

該当のエラー

エラー発生時の Lambda 関数

import json
import urllib3

def lambda_handler(event, context):
    request_url='https://sample1234567890.org/post'
    http = urllib3.PoolManager()
    
    body = {
    "jojo":"Jotaro"
    }
    
    headers = {
        'Content-Type': 'application/json'
    }
    
    response= http.request("POST",url=request_url, body=json.dumps(body))

    return response

実行ログ

{
  "errorMessage": "Unable to marshal response: Object of type HTTPResponse is not JSON serializable",
  "errorType": "Runtime.MarshalError",
  "requestId": "1234567890id",
  "stackTrace": []
}

原因と解決方法

レスポンスの型が悪かった

「Unable to marshal response: Object of type HTTPResponse is not JSON serializable」というエラーは、レスポンスオブジェクトが JSON シリアライズできないために発生します。具体的には、 Lambda 関数内で HTTP リクエストを送信し、そのレスポンスをそのまま返そうとした場合が考えられます。そのため、 HTTPResponse オブジェクトから、例えば、ステータスコードやレスポンスボディなどの情報を抽出し、シリアライズ可能な形式に整形してからレスポンスとして返す必要があります。

ハンドラーから json.dumps でシリアル化できないオブジェクトが返された場合、ランタイムはエラーを返します。

docs.aws.amazon.com

そもそも シリアライズ可能なオブジェクトとは?

このエラーの原因は、 Lambda 関数のレスポンスとして返されるオブジェクトが JSON シリアライズ可能でないためです。 シリアライズとは、オブジェクトをバイト列やテキストなどの形式に変換することを指します。 Python において JSON シリアライズ可能なオブジェクトとは、以下の通りです。

データ型
dict
list, tuple
str
int、float と int や float の派生列挙型
True
False
None

docs.python.org

解決方法

修正後ソースコード *戻り値にリクエストボディを指定しています。

import json
import urllib3

def lambda_handler(event, context):
    request_url='https://sample1234567890.org/post'
    http = urllib3.PoolManager()
    
    body = {
    "jojo":"Jotaro"
    }
    
    headers = {
        'Content-Type': 'application/json'
    }
    
    response= http.request("POST",url=request_url, body=json.dumps(body))
    
    return response.data

実行ログ

{
  "args": {},
  "data": "{\"jojo\": \"Jotaro\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "42",
    "Host": "httpbin.org",
    "User-Agent": "python-urllib3/1.26.15",
    "X-Amzn-Trace-Id": "Root=1234567890"
  },
  "json": {
    "jojo": "Jotaro"
  },
  "origin": "12.34.567.890",
  "url": "https://sample1234567890.org/post"
}

データ整形前後のデータ型の違いは以下の通りです。

response

( HTTPResponse オブジェクト)
<urllib3.response.HTTPResponse object at 0123345667890aaa>

response.data

(バイト列の文字列)
b'{\n  "args": {}, \n  "data": "{\\"jojo\\": \\"Jotaro\\"}", \n  "files": {}, \n  省略~}\n'

return response.data

(内部的に json.dumps 関数を実行)
{
  "args": {},
  "data": "{\"jojo\": \"Jotaro\"}",
  "files": {},
省略~
}

以上のようにレスポンスをそのまま返すのではなく何かしらの(要件に合わせた)整形する必要があります。今回は urllib3 ライブラリを使用した場合の例ですので、他のライブラリを使用する場合は適宜変更して下さい。

また、要件によって戻り値は必要なければ以下のように戻り値を Null にしてしまうのもありかなと思います。

 return None 

ちなみに指定しない場合も同じ挙動をするらしいです。

None ステートメントを指定しなかった場合の Python 関数の暗黙の動作と同じように、ハンドラーが return を返した場合、ランタイムは Null を返します。

docs.aws.amazon.com

まとめ

「Unable to marshal response: Object of type HTTPResponse is not JSON serializable」というエラーは、Lambda関数が非シリアライズ可能なオブジェクトをレスポンスとして返した際に発生します。このエラーを解決するためには、 HTTPResponse オブジェクトから必要なデータを取り出し、シリアライズ可能な形式に変換する必要があります。

このエラーを通して、受け取ったデータ型がどのようになっているか都度確認することがエラーを回避する近道なのかなと思いました。次回もまたお願いします。

外崎 隼斗 (記事一覧)

アプリケーションサービス部・DS2課

気になったことを書いていきます。

バイクとビールで生きていく!