Amazon S3 Select で扱う JSON データの形式と Type 指定について

S3 Select で扱うのに適している JSON の形式とは?その形式の JSON を取得する時に指定する Type は?これを読めば今ベストな組み合わせがわかります!
2018.04.27

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

はじめに こんばんは、菅野です。 先日のブログエントリーで、Amazon S3 Select を使って CSV ファイルを取得してみました。 今回は JSON データを対象にしてみたいと思います。

データが JSON の場合に指定するパラメータ

データが CSV の場合には存在しなかったパラメータとして「InputSerialization.JSON.Type」が必要となります。

Type:"Lines"

  • 1行が1レコードになっているタイプのデータを扱います
  • このタイプのデータを「JSON Lines」と呼びます(http://jsonlines.org/
{"id":1,"name":"りんご","price":90}
{"id":2,"name":"みかん","price":30}
{"id":3,"name":"柿","price":60}
{"id":4,"name":"キウイ","price":70}
{"id":5,"name":"梨","price":120}

Type:"Document"

  • JSON Lines 以外の JSON を扱う時はこちらを指定します
  • 以下のようにトップレベルがオブジェクトになっている JSON ファイルを扱う事ができます
{
"Records": [
{"id":1,"name":"りんご","price":90},
{"id":2,"name":"みかん","price":30},
{"id":3,"name":"柿","price":60},
{"id":4,"name":"キウイ","price":70},
{"id":5,"name":"梨","price":120}
]
}

トップレベルが配列になっている JSON ファイルについて

  • 以下のような JSON ファイルはどちらのタイプを使っても結果が空となります(2018年4月27日現在)
[
{"id":1,"name":"りんご","price":90},
{"id":2,"name":"みかん","price":30},
{"id":3,"name":"柿","price":60},
{"id":4,"name":"キウイ","price":70},
{"id":5,"name":"梨","price":120}
]

どちらのタイプを使うべきか(2018年4月27日現在)

「JSON Lines」を対象とし、Type:"Lines" で使いましょう。 JSON Lines 形式以外のデータに対して Type:"Document" でアクセスすると、以下のように全体で1レコードとして扱われますので where による絞り込みができません。

{"Records":[{"id":1,"name":"りんご","price":90},{"id":2,"name":"みかん","price":30},{"id":3,"name":"柿","price":60},{"id":4,"name":"キウイ","price":70},{"id":5,"name":"梨","price":120}]}

また、Type:"Lines" だと CSV 形式での出力も可能です。

取得用のスクリプト

準備については先日のブログエントリーを読んでください。 今回は「JSON Lines」に対して S3 Select を実行する python スクリプトを用意しました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import boto3
s3 = boto3.client( 's3', 'ap-northeast-1' )

response_s3select = s3.select_object_content(
Bucket = 'sugano-test-2',
Key = 'test-data/price-list-1.json',
ExpressionType = 'SQL',
Expression = 'Select * from S3Object',
# Expression = 'Select s.id, s.name, s.price from S3Object s',
# Expression = 'Select count(*) as data_count from S3Object s',
# Expression = 'Select s.id, s.name, s.price from S3Object s where cast(s.id as int) > 3',
# Expression = 'Select sum(cast(s.price as int)) as price_sum from S3Object s',
# Expression = 'Select max(cast(s.price as int)) as price_max, cast(avg(cast(s.price as int)) as int) as price_ave, min(cast(s.price as int)) as price_min from S3Object s',
InputSerialization = {
'CompressionType': 'NONE',
'JSON' : {
'Type' : 'Lines'
}
},
OutputSerialization = {
'CSV' : {
'RecordDelimiter' : '\n',
'FieldDelimiter' : ','
}
}
# OutputSerialization = {
# 'JSON' : {
# 'RecordDelimiter' : '\n'
# }
# }
)

for event in response_s3select[ 'Payload' ]:
if 'Records' in event:
records = event[ 'Records' ][ 'Payload' ].decode( 'utf-8' )
print( records )

結果(CSV出力)

1,りんご,90
2,みかん,30
3,柿,60
4,キウイ,70
5,梨,120

JSON で出力する場合

ソースコードの中に JSON 形式で出力するための OutputSerialization もコメントアウトで埋めてあります。 以下のように CSV 用と切り替えてみましょう。

# OutputSerialization = {
# 'CSV' : {
# 'RecordDelimiter' : '\n',
# 'FieldDelimiter' : ','
# }
# }
OutputSerialization = {
'JSON' : {
'RecordDelimiter' : '\n'
}
}

結果は以下のようになります。

{"id":1,"name":"りんご","price":90}
{"id":2,"name":"みかん","price":30}
{"id":3,"name":"柿","price":60}
{"id":4,"name":"キウイ","price":70}
{"id":5,"name":"梨","price":120}

他の SQL 文も試してみる

  • Select count(*) as data_count from S3Object s
{"data_count":5}
  • Select s.id, s.name, s.price from S3Object s where cast(s.id as int) > 3
{"id":4,"name":"キウイ","price":70}
{"id":5,"name":"梨","price":120}
  • Select sum(cast(s.price as int)) as price_sum from S3Object s
{"price_sum":370}
  • Select max(cast(s.price as int)) as price_max, cast(avg(cast(s.price as int)) as int) as price_ave, min(cast(s.price as int)) as price_min from S3Object s
{"price_max":120,"price_ave":74,"price_min":30}
  • 以下の SQL 文のように平均を int に cast していない場合は指数表記となるようです
  • Select max(cast(s.price as int)) as price_max, avg(cast(s.price as int)) as price_ave, min(cast(s.price as int)) as price_min from S3Object s
{"price_max":120,"price_ave":74e0,"price_min":30}

まとめ

JSON Lines 形式のデータも、CSV 形式と同様に S3 Select で扱える事が確認できました。 Type:"Document" の場合の動作は 2018年4月27日現在だと残念な結果となりましたが、いつか改善されると思いますので待ちましょう。

さいごに

いかがでしたでしょうか。 少しのソース修正だけで JSON Lines 形式のデータに対応することができました。 本エントリーがを見た皆様が S3 Select を活用するための参考になれば幸いです。

参考ページ

以下のページを参考にさせていただきました。 ありがとうございました。 Amazon S3 Select および Amazon Glacier Select の SQL リファレンス - Amazon Simple Storage Service