OutsystemsでCSVの読み込みと出力をしてみた

#はじめに
会社のITメンバーで参加している2019年のアドベントカレンダーに、自身２つ目の記事を投稿します！
自己紹介も兼ねて、前回書いた記事はこちらになります。
https://qiita.com/tom-k7/items/d8ef19dccb42891a0698

今回もOutsystemsについて書きます。
今はちょうどOutsystemsでCSV変換ツール（Webアプリ）を開発しているので、
OutsystemsでCSVの読み込み・出力の実装方法、あとは開発中にぶち当たった壁について共有させていただきます。
#やりたいこと
今開発しているツールでやりたいこととしては、
いろんな他社さんのサイトからダウンロードしたCSVファイルを
社内で使ってるシステムにインポートできるフォーマットのCSVファイルに変換したい。
なので、ダウンロードしたCSVファイルを読み込んで、
ごにょごにょ変換して新たなCSVファイルを出力するということをOutsystemsで実現します。
#Forge
CSVファイルの読み込み・出力にはCSVUtilのForgeコンポーネントを使います。
https://www.outsystems.com/forge/component-overview/636/csvutil

こちらをダウンロードし、ServiceStudioにインストールしてDependencyに追加してください。
#画面の開発
CSV処理をやる前に画面を作っちゃいます。
###CSVファイルのアップロード
まずは読み込む対象のCSVファイルをアップロードする必要があります。
方法は２つあります。
①Uploadを使う
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/262f6ad1-6f5c-0668-22de-1d653c3bb9b3.png)
②RichWidgets\Popup_Uploadを使う
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/09a842d9-63fa-a6ee-4f28-b4fffa8df376.png)


今回は①のほうが良いということになったので、Uploadを使います。
※ちなみにUploadウィジェットは、ListRecordsのようなリストの中では動かないのでご注意ください。
###変換処理を行うサーバアクションを呼ぶためのボタンを配置
ConvertボタンのDestinationに変換処理を行うConvertサーバアクションを指定し、
そのサーバアクション内で変換処理を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/03b48935-d20c-92d1-d92b-d2c562be2d57.png)
#CSV読み込み処理の実装
さて、ここからは本題のCSVの処理です。
まずはCSVファイルの読み込みを実装します。
###LoadConfigの設定
CSV読み込み時の設定をするためのCSVLoadConfig型のローカル変数を定義し、Assignで設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/eb93102f-85cc-fb51-8f01-fe907e4ebf62.png)
主な設定内容は以下の通り。

- Encode：CSVファイルの文字コード。utf-8やshift-jisなどを設定。
- IsSkipHeader：CSVファイルにヘッダー行がある場合はTrue、ない場合はFalse。
- IsIgnoreColumnChange：~~カラム数が合わない場合にエラーとする場合はFalse、エラーとしない場合はTrue。~~
　　　　　　　　　　　　試してみたら違いました。。このフィールド謎です"(-""-)"
- FieldDelimiter：カラムの間の区切り文字。複数文字入れられるが、先頭の１文字しか適用されない。
- IsDisableDoubleQuote：カラムがダブルクオーテーションで囲われてる場合はFalse、そうでない場合はTrue。

###読み込み用RecordListを定義
CSVを読み込んだ結果を格納するEntity/StructureのRecordのリストを、ローカル変数で定義します。
今回は自作のSourceCSVというStructureのRecordListにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/4891eccc-649b-8cd1-9f58-d5ad4997cb7a.png)
※StructureのListでなく、StructureのRecordのListにしないとうまくいきません。
###Extension処理の呼出し
ForgeのCSVUtilで定義されているLoadCSVRecordListサーバアクションを呼び出し、
引数には画面で作ったUploadのContentと、CSVLoadConfigのローカル変数、
Entity/StructureのRecordListをToObject()で変換した結果をそれぞれ設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/31bea512-39a8-41b5-7e16-500dc7f98bd8.png)
これで読み込みは完了です。
#CSV出力処理の実装
続いて、CSV出力のほうも実装していきます。
###ExportConfigの設定
CSV出力時の設定をするためのCSVExportConfig型のローカル変数を定義し、Assignで設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/b2037a5e-6e58-acee-868f-fc6486b597e7.png)
主な設定内容は以下の通り。

- IsShowHeader：CSVファイルにヘッダー行を入れる場合はTrue、入れない場合はFalse。
- FieldDelimiter：カラムの間の区切り文字。複数文字入れられるが、先頭の１文字しか適用されない。
- EncodeMode：カラムをダブルクオーテーションで囲うか否か。auto/quote/noquote/noquote_nocheckのいずれかを文字列で設定。
- LineSeparator：改行コード。CRだとChr(13)、LFだとChr(10)、CRLFはChr(13) + Chr(10)と設定。

###出力用RecordListを定義
出力するCSVのデータを格納するためのEntity/StructureのRecordのリストを、ローカル変数で定義します。
画像は自作のOutputCSVというStructureのRecordListにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/569a64b0-c4cc-77bd-ba72-e285bdedfbdb.png)
###出力用RecordListに読み込んだデータを変換して設定
↑で定義した出力用RecordListに対して、Loadしたデータが入っているRecordListの全件を設定します。
その際に値を変換したり、結合したり、不要なものは捨てたりして出力用に設定すれば変換ができますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/2522ea1a-38b0-f4ed-e247-cce6c5336c0e.png)
画像ではListAppendAllで単純に項目の結合だけしてますが、実際にはここでいろんな変換をします。
かなり複雑なことをやる場合はForEachでループするケースもあると思います。
###Extension処理の呼出し
CSVUtilで定義されているExportRecordList2CSVサーバアクションを呼び出し、
引数にはEntity/StructureのRecordListをToObject()で変換した結果とCSVExportConfigのローカル変数をそれぞれ設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/d7d2a213-af52-13df-80c7-6338a178ba42.png)
###変換後CSVファイルの出力（ダウンロード）
最後にExportRecordList2CSVで作成されたCSVデータをDownloadウィジェットに渡して終了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/7bc4c4c2-4173-412a-f0e0-17bd48483a9b.png)
#開発中に引っかかった罠
以上が実装方法になりますが、その他CSVUtilを使っててぶち当たった壁について共有したいと思います。
###出力時にダブルクオーテーションで囲う方法がわかりづらい
「ExportConfigの設定」でダブルクオーテーションで囲う方法はすでに記載済みですが、
そこにたどり着くまでに結構かかりました。
だって、その項目の名前EncodeModeて。。
文字コードのことかと思うやん。わからんて。。
ていう愚痴でしたww

ちなみにCSVExportConfig.EncodeModeに"quote"を指定するとダブルクオーテーションで囲ってくれます！
###Export処理でNullReferenceException
CSVUtilのExportRecordList2CSVサーバアクションを呼び出した際にこんなエラーが出ました。
`Object reference not set to an instance of an object.`
NullReferenceExceptionです。Javaでいうヌルポですね。
CSVの項目としては基本型しか使ってないので、Outsystemsで基本型でNullってあるんだっけ？って感じでした。

それでこのエラーはこれ以上詳細にはログに出ないので、いろいろ試してもうまくいかず途方に暮れた結果、
Extensionの中見るしかないな！ってなりました。
でExtensionダウンロードして、VisualStudioでsln開いてソース追っても原因わからず。
じゃあってことで、実際に渡してたデータをC#上で作って実行してみました!(^^)!（これが地味にめんどかった）

やってみた結果、渡したデータのカラムが１個足りてないせいでNullReferenceExceptionになっていたことが判明。
そのカラム何だろうと思ってたら、データ変換するときに特殊なことをしているカラムでした。

説明が意味不明だったらすいません<m(__)m>
例えば読み込んだデータの値が"1"なら、変換後は"大学院"とするみたいなものをJSONファイルにKeyとValueのペアとして定義し、
JSONファイルをResourcesとしてモジュールにインポート。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380120/a61c0868-edf8-a16f-31b6-5b2ba3c1552b.png)

そのJSONファイルをJSONDeserializeで読み込み、ForgeのHashTableを使ってKeyとValueのペアとしてメモリに格納しておき、
変換時はそのHashTableからgetした値を設定するという処理にしていました。

ForgeのHashable：https://www.outsystems.com/forge/component-overview/21/hashtable

それが、JSONで定義したKeyにはないデータ（↑のJSONだと"5"とか空文字とか）が読み込むCSVに入っていたため、getの結果がNullとなり、
それをExportRecordList2CSVに渡したためにNullReferenceExceptionとなったということでした。

Outsystemsで普通に実装してたらText型にNullは入らないのですが、
Extensionを介すとNullが入るんだなって知り、勉強になったなと思いました。
#最後に
OutsystemsでのCSV読み込み・出力について説明しました。
これからCSVUtilを使う方、同じ壁にぶつかった方の一助になればと思っています。