More than 1 year has passed since last update.

設定ファイルにyamlを使う

私はジャバの方から来たので設定ファイルといえばxmlでした。でもxmlは人に優しくない。jsonだとコメント入れられないのでyamlを使いたい。

ScalaではHOCON形式がよく使われるのでknobsも気になる。

「scala yaml」で検索してみる

Java向けのSnakeYamlを使う方法が見つかりました。
http://alvinalexander.com/scala/scala-yaml-parser-parsing-examples-snakeyaml-objects
しかしメンバーをvarにして@BeanPropertyつけるのは具合が悪いです。普通のimmutableなcase classを使いたい。

HelicalYamlなるものも見つけましたが「The project is not maintained at the moment.」と宣言されてます。

Scalaに来たけどJavaのプロダクトにも関わってるし、jsonとかyamlとか他いろいろ一つのライブラリの知識で対応できたらいいのになあ。そんなに都合のいいモノなんてないよなあ、と思っていたらありました。

jacksonの拡張モジュール

jackson-dataformat-yaml yaml用データフォーマット
jackson-module-scala scalaクラスに読み込むためのモジュール

jacksonのObjectMapperのインスタンスにScalaモジュールを登録してやるとcase classへの読み込みができます。

val mapper = new ObjectMapper(new YAMLFactory())
mapper.registerModule(DefaultScalaModule)
val myCaseClass = mapper.readValue[MyCaseClass](yaml)

プロジェクトではこんな感じのクラスを作って設定ファイルを読み込みました。

Object Yaml {
  private[this] val mapper = new ObjectMapper(new YAMLFactory).registerModule(DefaultScalaModule)

  def read[T](filename: String, clazz: Class[T]):T ={
    log.debug("Yaml read ["+ filename +"]")
    resource(filename) match {
      case Some(f) => mapper.readValue(f,clazz)
      case None => throw new RuntimeException("File not found." + filename)
    }
  }

  def read[T](filenames: Seq[String], clazz: Class[T]):T ={
    for(f <- filenames; r <- resource(f)) return mapper.readValue(r, clazz)
    throw new RuntimeException("File not found." + filenames)
  }

  private def resource(filename: String):Option[URL] = {
    val f = getClass.getClassLoader.getResource(filename)
    if(f!=null) Some(f) else None
  }

Seqを取る方は「config-test.yamlがあればそちらを優先、なければconfig.yamlを探す」みたいな使い方をします。

戻り型をOption[T]にして見つからなかったらNoneを返すか迷いました。設定ファイルが必ず必要なプロジェクトなので見つからない場合は例外を投げるようにしました。

※2016/01/15更新
getResourceAsStream()だと自分でclose()しないといけないので変更しました。複数ファイルをとる方はfor式にしたら簡単になりました。

jacksonのページを見るとProtocolBufferにも対応してるしHOCONも'work in progress'ってなっているのでjacksonだけ押さえておけばなんでもできそうですね。

これならJavaもOKさ!(歯を大切に