Reading a YAML Configuration File in Kotlin

Picture of author

By Sean Soper

October 13, 2020

Photo by CHUTTERSNAP on Unsplash.

Introduction

During the heyday of Ruby development 10 years ago the YAML format was all the rage. But as developers moved towards Javascript-powered frameworks like node the preferred format for configuration files moved with them to JSON. It was the Java community, which had been enthrall to XML for the better part of a decade, that began to roll out better support for YAML.

This post builds on work previously done in Reading from the Command-Line in Kotlin.

Structure

While command-line switches can take you pretty far they eventually become cumbersome to manage and it’s at this point that I like to introduce a configuration file. Ours will be in YAML format and will reference account information that is required to obtain an E*TRADE token. I’m also aiming to keep it extensible should we want to access other brokerage accounts.

Here is the basic structure:

etrade:
  sandbox:
    key: value
    secret: value
  production:
    key: value
    secret: value
  username: value
  password: value

When creating a data class to map to this structure we should start with the leaf nodes.

data class EtradeAuth(val key: String,
                      val secret: String)

This single class can be used for both the sandbox and production values.

data class EtradeConfiguration(val sandbox: EtradeAuth,
                                val production: EtradeAuth,
                                val username: String,
                                val password: String)

data class Configuration(val etrade: EtradeConfiguration

Note that none of these fields are optional or have default values so all the fields are required if we are to ingest the YAML file successfully.

Jackson

I don’t have a whole lot to add to these already fantastic blog posts on integrating and using Jackson in your project. But I do want to note some of the steps necessary to ensure the JAR file you build has all the required dependencies.

Since we are using Maven we can search for the Jackson dependency and add them to our Gradle file. At a minimum we will need the Kotlin module and YAML dataformat either of which will pull in the main Jackson dependency.

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib")

    // These are the new dependencies
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.2")
    implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.2")
}

Note that the string is split into three parts with a : as the separator. The version specification, which follows semver, is unnecessary if you want to let the version point to whatever is the latest available.

Ingest

Let’s create a new class IngestConfiguration where we can centralize this functionality. Using some of the fantastic pathing and file functionality provided by Java out of the box, we can use the current directory to search for a file called batil.yaml, read it in and pass it to Jackson to parse.

fun parse(): Configuration {
    val path = Paths.get(basePath, "batil.yaml")
    val mapper = ObjectMapper(YAMLFactory())
    mapper.registerModule(KotlinModule())

    return try {
        Files.newBufferedReader(path).use {
            mapper.readValue(it, Configuration::class.java)
        }
    } catch (exception: MissingKotlinParameterException) {
        println("Could not read YAML file!")
        println(exception.message)
    }
}

To ensure our JAR file has all the necessary dependencies we’ll want to go into our Project StructureArtifacts, select the dependencies that came down with Jackson, right click and select Extract Into Output Root. Without this step the JAR will still compile but will fail when it attempts to ingest the YAML files.

Structure Screenshot

Note that after adding a dependency you may need to reaffirm the Inherit project compile output path radio button selection under Modules. You’ll also want to update your .gitignore to avoid accidentally committing any tokens that should remain a secret. And as always, a summary of these commits can be found here.