【Tips】Lambda(Python)でハンドラーのeventを全て辞書型だと思っていたらハマったこと

2016.11.17

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

森永です。
物凄いハマったんですが、解決してみればそこかよという案件です。
Lambda使いこなしている人だとあるあるなのかもしれません。

問題

イベントソースがSNSで起きた事象です。(他のイベントソースでも起こる可能性はあります。)
SNSからは以下のようなjsonがイベントとして送られてきます。(かなり端折ってます)
いや、送られてくると思ってました。

{
  "Records": [ {
    "Sns": {
      "Timestamp": "2016-11-17T08:34:04.436Z", 
      "Message": {
        "Trigger": {
          "Dimensions": [ { 
            "name": "key", 
            "value": "value"
          } ]
        } 
      }
    }
  } ]
}

Lambdaのハンドラーにeventというパラメータがあります。 イベントソースから送られてきた情報などが記載されているのですが、基本的に辞書型です。

def lambda_handler(event, context):
    print type(event)

=> <type 'dict'>

辞書型なので、以下のように下の階層を取り出してみても辞書型になってます。

def lambda_handler(event, context):
    print type(event['Records'][0])

=> <type 'dict'>

もっと深く掘ってみましょう!!

def lambda_handler(event, context):
    print type(event['Records'][0]['Sns'])
    print type(event['Records'][0]['Sns']['Message'])

=> <type 'dict'>
=> <type 'unicode'>

え?????
「Message」に潜った瞬間に何故かunicodeになりました。

unicodeだと何が問題かというと、「Message」の下の「Trigger」にアクセスしようしても辞書型ではないので、event['Records'][0][Sns][Message][Trigger]とすることが出来ないということです。
最初はこれをやろうとして、TypeError: string indices must be integersというエラーが出て詰まっていました。

原因究明

突如としてunicodeになるのは何故か。
Python詳しい人ならすぐに思いつくことかと思いますが、殆ど触っていない私はまぁハマりました。

最終的にeventをprintしたものを隅々まで確認して遂に原因が判明しました。

{
    u'Records': [ {
        u'Sns': {
            u'Timestamp': u'2016-11-17T08:34:04.436Z', 
            u'Message': u'{
                "Trigger": {
                    "Dimensions": [ { 
                        "name": "sample", 
                        "value": "sample"
                    } ]
                } 
            }'
        }
    } ]
}

eventをそのままprintすると各項目がUnicode文字列であるため、uが項目の前に付与されています。
一見何も問題なさそうですが、よく見ると「Message」のあとの項目にはuが付いていません。
そして何故か「Message」の後の波括弧の前にUnicode文字列を表すuが…

そう、「Message」のあとの値(波括弧で囲まれた部分)が全て文字列として扱われていたのです!!!!
つまりはこういうことです。

{
  "Records": [ {
    "Sns": {
      "Timestamp": "2016-11-17T08:34:04.436Z", 
      "Message": "{ \"Trigger\": { \"Dimensions\": [ { \"name\": \"key\", \"value\": \"value\" } ] } }"
    }
  } ]
}

これはアカン

解決方法

SNSから送られてくる文字列を変えることは出来ないので、Lambda側で対応しました。 Unicode文字列だと使えないのであれば、使える形の辞書型に変換しちゃえばよいのです。 変換にはjson.loadsを使用しました。

def lambda_handler(event, context):
    # このままだとUnicode文字列になっちゃう
    message_unicode = event['Records'][0]['Sns']['Message']
    # json.loadsでUnicode→辞書型変換をするといける!
    message_dist = json.loads(message_unicode)

    print type(message_unicode)
    print type(message_dist)
    # 辞書形になれば以下のように下の階層を参照することも可能です。
    print type(message_dist['Trigger'])

=> <type 'unicode'>
=> <type 'dict'>
=> <type 'dict'>

他にいい方法あったら教えてください。

さいごに

Lambdaは触る度にハマって足が遠のくので、ガンガン触ってもっと仲良くなろうと思います。
同じ症状で困っている方のお役に立てれば何よりです。