vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Kotlin】【Windows】JavaFX + Apache POI + GsonでExcelからJsonファイルを作る2 (1の修正)

はじめに

前回で一応Excelからデータを取得してJsonファイルに書き出す、ということができるようになりました。
その時以下の内容で出力するようにしていました。

{
    data1:{
        "element1": "element1"
        ,"element2": "element2"
    }
    , data2{
        "element1": "element1"
        ,"element2": "element2"
    }
~省略~

しかし、Androidのアプリで同じくGsonを使ってこのJsonを読み込もうとしたときに、以下のような配列として出力されている方が良い、ということがわかってきました。

{
    data:[
        {
            "element1": "element1"
            ,"element2": "element2"
        }
        , {
            "element1": "element1"
            ,"element2": "element2"
        }
~省略~
    ]
}

ということで今回は、Excelから読み込んだデータを配列として出力する方法についてです。

データをセットするクラスを使用する

配列を作るには、専用のクラスを作成してその配列をJsonに変換 → 出力 という方法が考えられます。

ToiletInfo.kt

class ToiletInfo {
    var toiletName = ""
        get set
    var district = ""
        get set
    var municipality = ""
        get set
    var address = ""
        get set
    var latitude: Double = 0.0
        get set
    var longitude: Double = 0.0
        get set
    var availableTime = ""
        get set
    var hasMultiPurposeToilet: Boolean = false
        get set
}

このクラスを使って、Excelから読み込んだデータをセットした配列を作成します。

SpreadsheetAccesser.kt

class SpreadsheetAccesser {

    lateinit var ToiletInfoList: ArrayList< ToiletInfo >
        get

~省略~
    fun loadFile(targetFilePath: String, targetSheetName: String){
        val fileStream = FileInputStream(targetFilePath)
        val currentWorkbook = WorkbookFactory.create(fileStream)

        if(currentWorkbook == null){
            return
        }
        val targetSheet: Sheet? = currentWorkbook.getSheet(targetSheetName)
        if(targetSheet == null){
            return
        }
        val rowCount = targetSheet.physicalNumberOfRows - 1
        if(rowCount < 0){
            return
        }
        // 最初の行から列数を取得する.
        if(targetSheet.getRow(0).physicalNumberOfCells >= 9) {
            ToiletInfoList = ArrayList()

            // 最初の行は項目名なのでスキップ.
            for (i in 1..rowCount) {
                val toiletInfo = ToiletInfo()
                // 1. toiletName, 2. district, 3. municipality, 4. address,
                // 5. latitude, 6. longitude, 7.availableTime, 8.hasMultiPurposeToilet.
                toiletInfo.toiletName = targetSheet.getRow(i).getCell(1).stringCellValue
                toiletInfo.district = targetSheet.getRow(i).getCell(2).stringCellValue
                toiletInfo.municipality = targetSheet.getRow(i).getCell(3).stringCellValue
                toiletInfo.address = targetSheet.getRow(i).getCell(4).stringCellValue
                toiletInfo.latitude = targetSheet.getRow(i).getCell(5).numericCellValue
                toiletInfo.longitude = targetSheet.getRow(i).getCell(6).numericCellValue
                var availableTime: String? = targetSheet.getRow(i).getCell(7)?.stringCellValue
                availableTime = availableTime?: ""
                toiletInfo.availableTime = availableTime
                toiletInfo.hasMultiPurposeToilet = targetSheet.getRow(i).getCell(8).booleanCellValue

                ToiletInfoList.add(toiletInfo)
            }
        }
        currentWorkbook.close()
        fileStream.close()
    }
}

あとはJsonに変換し、出力します。

JsonFileCreater.kt

class JsonFileCreater {
    fun createFile(toiletInfoList: ArrayList< ToiletInfo >, fileTitle: String){
        val stringWriter = StringWriter()
        val jsonWriter = JsonWriter(BufferedWriter(stringWriter))
        // 出力したJsonファイルで適切にインデントが入る...はずだが今回は効いていないようです.
        jsonWriter.setIndent("  ")
        
        jsonWriter.beginObject()

        val gson = Gson()
        
        // 「jsonWriter.name("").value("")」のvalueにはArrayListを入れられないため、Jsonに変換した上でJsonデータとしてセット.
        jsonWriter.name("toiletInfo").jsonValue(gson.toJson(toiletInfoList))

        jsonWriter.endObject()
        jsonWriter.close()
        val createdJson = String(stringWriter.buffer)

        try{
            val splittedTitles = fileTitle.split('.')
            if(splittedTitles.size <= 0){
                return
            }
            val fileWriter = FileWriter(splittedTitles[0] + ".json")
            fileWriter.write(createdJson)
            fileWriter.close()
            
            // TODO: 出力が終わったことを知らせる.
            
        }catch(e: IOException){
            // TODO: 適切なエラー処理.
        }
    }
}

出力した結果は以下のようになります。

{

    "toiletInfo": [
        {
            "toiletName": "友ヶ島野奈浦公衆トイレ",
            "district": "和歌山県",
            "municipality": "和歌山市",
            "address": "和歌山市加太笘ヶ沖島2673-3",
            "latitude": 34.282891,
            "longitude": 135.008677,
            "availableTime": "終日",
            "hasMultiPurposeToilet": true
        },
        {
            "toiletName": "友ヶ島南垂水公衆トイレ",
            "district": "和歌山県",
            "municipality": "和歌山市",
            "address": "和歌山市加太苫ケ沖島2673-1",
            "latitude": 34.28255,
            "longitude": 135.013597,
            "availableTime": "終日",
            "hasMultiPurposeToilet": false
        },
~省略~

HashMapを使う(失敗)

上記の方法を取ると確かにやりたいことの実現はできたのですが、読み込むセルの列数やデータ型などが固定されてしまうため少々不便でもあります。

それを解決する方法を調べていたのですが、HashMap使えるんじゃね?と思い至り、試してみることにしました。

 SpreadsheetAccesser.kt

import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.apache.poi.ss.usermodel.Cell
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.WorkbookFactory
import java.io.FileInputStream
import java.util.*

class SpreadsheetAccesser {

    lateinit var LoadedSheetItemList: ArrayList < ArrayList < HashMap < String, Any > > >
        get

~省略~
    fun loadFile(targetFilePath: String, targetSheetName: String){
        val fileStream = FileInputStream(targetFilePath)
        val currentWorkbook = WorkbookFactory.create(fileStream)

        if(currentWorkbook == null){
            return
        }
        // シート名から対象のシートを取得する.
        val targetSheet: Sheet? = currentWorkbook.getSheet(targetSheetName)
        if(targetSheet == null){
            return
        }
        // セルに何らかの値が含まれる行数の取得.
        val rowCount = targetSheet.physicalNumberOfRows - 1
        if(rowCount < 0){
            return
        }
        // 最初の行から列数を取得する.
        val columnCount = targetSheet.getRow(0).physicalNumberOfCells - 1

        // 最初の行をタイトル行としてArrayListを作成する.
        val ColumnTitleList = ArrayList < String > ()
        for(cell in targetSheet.getRow(0)){
            ColumnTitleList.add(getCellValue(cell).toString())
        }
        // 実際の値が入ったセルの値をセットするArrayListの生成.
        LoadedSheetItemList = ArrayList < ArrayList < HashMap < String, Any > > > ()

        // タイトル行から取得した列数 ✕ (セルに値が含まれる行数 - 1)の値をセットする.
        for(i in 1..rowCount){
            val loadedRowItemList = ArrayList < HashMap < String, Any > > ()

            for(t in 1..columnCount){
                loadedRowItemList.add(hashMapOf < String, Any > (ColumnTitleList[t] to getCellValue(targetSheet.getRow(i).getCell(t))))
            }
            LoadedSheetItemList.add(loadedRowItemList)
        }
        currentWorkbook.close()
        fileStream.close()
    }
    fun getCellValue(targetCell: Cell?): Any{
        var result = ""
        if(targetCell == null){
            return result
        }
        // SpreadsheetにおけるCellの型に合わせて値を返す.
        when(targetCell.cellType){
            Cell.CELL_TYPE_BOOLEAN -> return targetCell.booleanCellValue
            Cell.CELL_TYPE_NUMERIC -> return targetCell.numericCellValue
            Cell.CELL_TYPE_STRING -> return targetCell.stringCellValue
            Cell.CELL_TYPE_FORMULA -> return targetCell.cellFormula
        }
        return result
    }
}

JsonFileCreater.kt

import com.google.gson.Gson
import com.google.gson.stream.JsonWriter
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.IOException
import java.io.StringWriter
import java.util.*

class JsonFileCreater {
    fun createFile(toiletInfoList: ArrayList < ArrayList < HashMap < String, Any > > >, fileTitle: String){
        val stringWriter = StringWriter()
        val jsonWriter = JsonWriter(BufferedWriter(stringWriter))
        jsonWriter.setIndent("  ")
        jsonWriter.beginObject()

        val gson = Gson()
        jsonWriter.name("toiletInfo").jsonValue(gson.toJson(toiletInfoList))

        jsonWriter.endObject()
        jsonWriter.close()
        val createdJson = String(stringWriter.buffer)

        try{
            val splittedTitles = fileTitle.split('.')
            if(splittedTitles.size <= 0){
                return
            }
            val fileWriter = FileWriter(splittedTitles[0] + ".json")
            fileWriter.write(createdJson)
            fileWriter.close()
        }catch(e: IOException){

        }
    }
}

で、これを実行するとどうなるかというと、

{

    "toiletInfo": [
        {
            "toiletName": "友ヶ島野奈浦公衆トイレ"
        },
        {
            "district": "和歌山県"
        },
        {
            "municipality": "和歌山市"
        },
        {
            "address": "和歌山市加太笘ヶ沖島2673-3"
        },
        {
            "latitude": 34.282891
        },
        {
            "longitude": 135.008677
        },
        {        
            "availableTime": "終日"
        },
        {
            "hasMultiPurposeToilet": true
        },
        {
            "toiletName": "友ヶ島南垂水公衆トイレ"
        },
        {
            "district": "和歌山県"
        },
        {
            "municipality": "和歌山市"
        },
        {
            "address": "和歌山市加太苫ケ沖島2673-1"
        },
        {
            "latitude": 34.28255
        },
        {
            "longitude": 135.013597
        },
        {
            "availableTime": "終日"
        },
        {
            "hasMultiPurposeToilet": false
        },
~省略~

う~ん、なんか違う(´;ω;`)

何かしら解決方法はありそうですが、今回調べただけではよくわかりませんでしたorz

これについては、今後解決方法が見つかったらこのブログで報告したいと思います。

参考

Gson

Kotlin