2019年3月17日日曜日

JacksonでCSVの読み込み

ヘッダなしCSVファイルの読み込み

CsvSchemaでカラムを定義してBeanとマッピングする。
fun main(args: Array) {
    val csvSchema = CsvSchema.builder()
            .addColumn("id")
            .addColumn("name")
            .build()
    val csvMapper = CsvMapper()
    csvMapper.registerModule(KotlinModule())

    val csvFile = csvMapper.readerFor(CsvFile::class.java)
            .with(csvSchema)
            .readValues("""
                1,test
                2,test2
                3,test3
            """.trimIndent())
    
    csvFile.forEach {
        println("it = ${it}")
    }
    
}

data class CsvFile(
        val id:Long,
        val name:String
)

ヘッダありCSVファイルの読み込み

Schemaに対して、useHeaderを設定するとヘッダありファイルを読み込める。
ヘッダの内容をもとにプロパティとのマッピングが行われるので、ヘッダなしのときのようなSchemaでのカラム定義が必要なくなる。
fun main(args: Array) {
    val csvSchema = CsvSchema.builder()
            .setUseHeader(true)
            .build()
    val csvMapper = CsvMapper()
    csvMapper.registerModule(KotlinModule())

    val csvFile = csvMapper.readerFor(CsvFile::class.java)
            .with(csvSchema)
            .readValues("""
                id,name
                1,test
                2,test2
                3,test3
            """.trimIndent())
    
    csvFile.forEach {
        println("it = ${it}")
    }
    
}

data class CsvFile(
        val id:Long,
        val name:String
)

ヘッダ名とプロパティ名が一致しない場合の読み込み

ヘッダなしのファイルと読み込む場合と同じように、カラム定義が必要となる。
カラム定義を行わず、対象のプロパティに対して@JsonProperty("なまえ")とすることでもマッピングができる。
fun main(args: Array) {
    val csvSchema = CsvSchema.builder()
            .addColumn("id")
            .addColumn("name")
            .setUseHeader(true)
            .build()
    val csvMapper = CsvMapper()
    csvMapper.registerModule(KotlinModule())

    val csvFile = csvMapper.readerFor(CsvFile::class.java)
            .with(csvSchema)
            .readValues("""
                id,なまえ
                1,test
                2,test2
                3,test3
            """.trimIndent())
    
    csvFile.forEach {
        println("it = ${it}")
    }
    
}

data class CsvFile(
        val id:Long,
        val name:String
)

クォートで囲まれている要素の読み込み

QuoteCharでクォート文字を設定することで、その文字で囲まれている要素を読み込める。
fun main(args: Array) {
    val csvSchema = CsvSchema.builder()
            .setUseHeader(true)
            .setQuoteChar('"')
            .build()
    val csvMapper = CsvMapper()
    csvMapper.registerModule(KotlinModule())

    val csvFile = csvMapper.readerFor(CsvFile::class.java)
            .with(csvSchema)
            .readValues("""
                id,なまえ
                1,"test"
                2,"te""st2"
                3,"test3"
            """.trimIndent())
    
    csvFile.forEach {
        println("it = ${it}")
    }
    
}

data class CsvFile(
        val id:Long,
        @JsonProperty("なまえ")
        val name:String
)

Beanに定義されていないカラムを無視して読み込む

FAIL_ON_UNKNOWN_PROPERTIESをfalseにすることで、未定義のプロパティがあったときでも無視して読み込みが行われる。
fun main(args: Array) {
    val csvSchema = CsvSchema.builder()
            .setUseHeader(true)
            .setQuoteChar('"')
            .build()
    val csvMapper = CsvMapper().apply {
        registerModule(KotlinModule())
        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    }

    val csvFile = csvMapper.readerFor(CsvFile::class.java)
            .with(csvSchema)
            .readValues("""
                id,なまえ,そのた
                1,"test",hoge
                2,"tes2",fuga
                3,"test3",piyo
            """.trimIndent())
    
    csvFile.forEach {
        println("it = ${it}")
    }
    
}

data class CsvFile(
        val id:Long,
        @JsonProperty("なまえ")
        val name:String
)










2019年2月3日日曜日

ElasticsearchのクエリDSLでand検索

ElasticsearchのクエリDSLでのand検索

インデクスの内容


 curl -H 'Content-Type:application/json' http://localhost:9200/test_index/_search -d '{"query": {"match_all":{}}}' | jq '.hits.hits'
[
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "1",
    "_score": 1,
    "_source": {
      "user_name": "test user1"
    }
  },
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "2",
    "_score": 1,
    "_source": {
      "user_name": "test user2"
    }
  }
]

デフォルトでのmatchクエリ

デフォルトでは、検索条件で指定したusertest1のOR検索になるので、test user2もヒットする。

curl -H 'Content-Type:application/json' http://localhost:9200/test_index/_search -d '{"query": {"match":{"user_name": "test user1"}}}' | jq '.hits.hits'
[
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "1",
    "_score": 0.87546873,
    "_source": {
      "user_name": "test user1"
    }
  },
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "2",
    "_score": 0.18232156,
    "_source": {
      "user_name": "test user2"
    }
  }
]

AND検索

operatorにandを指定するとAND条件に変更でき、検索結果からuser2のデータが消える。

curl -H 'Content-Type:application/json' http://localhost:9200/test_index/_search -d '{"query": {"match":{"user_name": {"query":"test user1", "operator":"and" }}}}' | jq .hits.hits
[
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "1",
    "_score": 0.87546873,
    "_source": {
      "user_name": "test user1"
    }
  }
]

その他の指定

minimum_should_matchで、指定したキーワードのうち何個マッチ以上マッチしたものを返すことができる。
この場合は、1を指定しているので1つ以上マッチしたものがヒットする

 curl -H 'Content-Type:application/json' http://localhost:9200/test_index/_search -d '{"query": {"match":{"user_name": {"query":"test user1", "minimum_should_match":1 }}}}' | jq .hits.hits
[
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "1",
    "_score": 0.87546873,
    "_source": {
      "user_name": "test user1"
    }
  },
  {
    "_index": "test_index",
    "_type": "test",
    "_id": "2",
    "_score": 0.18232156,
    "_source": {
      "user_name": "test user2"
    }
  }
]

2019年1月27日日曜日

Jacksonでsnake caseのキーをlower camel caseのプロパティーにデシリアライズする

環境


implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"

ObjectMapperの設定をする

ObjectMapperにPropertyNamingStrategyを設定します。
Jsonのキーの名前はsnake_caseなので、PropertyNamingStrategy.SNAKE_CASEを設定します。
val objectMapper = jacksonObjectMapper().apply { 
    setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
}

確認コード

動作確認用のコードです。
fun main(args: Array) {
    val objectMapper = jacksonObjectMapper().apply { 
        setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
    }
    
    val json = """{
        |"user_name": "なまえ"
        |}""".trimMargin()

    val user = objectMapper.readValue(json)
    println("user = ${user}")

    val string = objectMapper.writeValueAsString(user)
    println("string = ${string}")

}
data class User (val userName:String?)

実行結果

user = User(userName=なまえ)
string = {"user_name":"なまえ"}

2018年12月29日土曜日

Ubuntuのターミナルでファイルのフルパスを取得する方法

readlinkコマンドを使うとファイルのフルパスを取得できる。

取得したいファイルを、-fオプションに指定する。
$ readlink -f hoge.txt 
/home/hoge/hoge.txt

2018年12月26日水曜日

Elasticsearchに格納しているデータの一部を書き換える方法

Elasticsearch内のデータの一部の書き換えはUpdate APIを使います。
ドキュメントは、Updates with a partial documentあたりになります。

確認

書き換え対象のデータの内容です。hoge1とhoge2のキーを持っています。
$ curl -s http://localhost:9200/test_index/hoge/1 | jq
{
  "_index": "test_index",
  "_type": "hoge",
  "_id": "1",
  "_version": 3,
  "found": true,
  "_source": {
    "hoge1": "hoge1",
    "hoge2": "fuga2"
  }
}


Update APIを叩いて、hoge1の値を変更してみます。
公式ドキュメントに従い、POSTで変更対象のidの_updateを叩きます。
body部には、jsonでdoc配下に変更したい内容を定義して送ります。
$ curl -XPOST -H "Content-Type: application/json" http://localhost:9200/test_index/hoge/1/_update -d ' 
{
  "doc": {
    "hoge1": "mod-hoge1"
  }
}
'

書き換えたデータを取得すると、データの内容が変更されていることが確認できます。
$ curl -s http://localhost:9200/test_index/hoge/1 | jq
{
  "_index": "test_index",
  "_type": "hoge",
  "_id": "1",
  "_version": 4,
  "found": true,
  "_source": {
    "hoge1": "mod-hoge1",
    "hoge2": "fuga2"
  }
}

2018年12月14日金曜日

docker-compose内のservice一覧を表示する方法

docker-compose config --servicesでサービス名の一覧を出力できる。

2018年10月2日火曜日

GoでCSVファイルを読み込む

Package csvを使ってCSVファイルを読み込む例になります。

読み込むCSVファイルを準備する

文字列は、「"」で囲み要素内に「,」がある内容としてみました。
"name","age"
"hoge,fuga", 100

読み込んで見る

package main

import (
 "encoding/csv"
 "fmt"
 "io"
 "log"
 "os"
)

func main() {

        // ファイルを開く
 file, err := os.Open("test.csv")
 if err != nil {
  log.Fatal(err)
 }

        // 開いたファイルの後始末
 defer file.Close()

        // CSVを読み込むためのReaderを生成
 reader := csv.NewReader(file)

 for {
                // 1行づつ読み込む
  record, err := reader.Read()
                // ファイルの末尾で処理終了
  if err == io.EOF {
   break
  }
  if err != nil {
   log.Fatal(err)
  }
  fmt.Println(record)
 }

}

出力結果

クォートが削除されて読み込まれ、要素内の「,」も正しく読み込まれていることがわかります。
[name age]
[hoge,fuga  100]