2019年7月15日月曜日

KotlinのHttp ClientのFuelでレスポンスをJacksonでデシリアライズする

FuelでレスポンスのjsonをJacksonでデシリアライズする方法

依存ライブラリ

  implementation("com.github.kittinunf.fuel:fuel:2.1.0")
  implementation("com.github.kittinunf.fuel:fuel-coroutines:2.1.0")
  implementation("com.github.kittinunf.fuel:fuel-jackson:2.1.0")

サンプル

responseObjectにjacksonDeserializerOfを指定する子でJacksonを使ったデシリアライズが行われる。
fun main() {
    "https://jsonplaceholder.typicode.com/todos/1"
            .httpGet()
            .responseObject(jacksonDeserializerOf()) { request, response, result ->
                println("response.statusCode = ${response.statusCode}")
                println("result.component1() = ${result.component1()}")
                println("result.component2() = ${result.component2()}")
            }
            .join()
}

data class Result(
        val userId:Int,
        val id: Int,
        val title: String,
        val completed: Boolean
)

2019年6月15日土曜日

Spring data JPAで範囲指定の検索

範囲指定で検索したい場合は、検索メソッドにPageableを渡してあげると良い。
ロジックで、Pageableを生成したい場合は、以下のようにPageRequest.ofを使う。

val pageRequest = PageRequest.of(0, 5)
val result = testRepository.findAll(pageRequest)

2019年4月13日土曜日

Elasticsearchのインデックス内の全データの削除

削除対象のインデックスの内容

curl -H 'Content-Type: application/json' http://localhost:9200/test/_search\?pretty -d '{"query":{"match_all":{}},"size":0}'
{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 101,
    "max_score" : 0.0,
    "hits" : [ ]
  }
}

データの削除

_delete_by_queryを使ってデータを削除する。
curl -X POST -H 'Content-Type: application/json' http://localhost:9200/test/_delete_by_query?pretty -d '{"query":{"match_all":{}}}'
{
  "took" : 106,
  "timed_out" : false,
  "total" : 101,
  "deleted" : 101,
  "batches" : 1,
  "version_conflicts" : 0,
  "noops" : 0,
  "retries" : {
    "bulk" : 0,
    "search" : 0
  },
  "throttled_millis" : 0,
  "requests_per_second" : -1.0,
  "throttled_until_millis" : 0,
  "failures" : [ ]
}

削除確認

curl -H 'Content-Type: application/json' http://localhost:9200/test/_search\?pretty -d '{"query":{"match_all":{}},"size":0}'
{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : 0.0,
    "hits" : [ ]
  }
}

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":"なまえ"}