github linkedin
SnakeYAML and Kotlin
2016-02-13

Lately I was trying to read a config file with Kotlin. The config file is written in YAML and I’m using SnakeYAML for reading the file.

Consider the following file:

# Size of the universe
universeSize:
  maxGalaxies: 1
  maxSystems: 3
  maxPlanets: 3

# Starter planet settings
starterPlanet:
  # Starting resources
  resources:
    crystal: 200
    gas: 100
    energy: 800

# Round time in seconds
roundTime: 5

How to model the class hierarchy so that my Kotlin program can read the files? First I tried Kotlin data classes:

data class UniverseSizeDto(var maxGalaxies: Int, var maxSystems: Int, var maxPlanets: Int)

data class ResourcesDto(var crystal: Int, var gas: Int, var energy: Int)

data class StarterPlanetDto(var resources: ResourcesDto)

data class ConfigDto(var universeSize: UniverseSizeDto, var starterPlanet: StarterPlanetDto, var roundTime: Int)

Reading the config file is relatively easy:

fun loadFromFile(path: Path): ConfigDto {
    val yaml = Yaml(Constructor(ConfigDto::class.java))
    return Files.newBufferedReader(path).use {
        yaml.load(it) as ConfigDto
    }
}

Unfortunately this results in the following exception:

Exception in thread "main" Can't construct a java object for tag:yaml.org,2002:restwars.business.config.Config$ConfigDto; exception=java.lang.NoSuchMethodException:     restwars.business.config.Config$ConfigDto.<init>()
in 'reader', line 2, column 1:
universeSize:
^

at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:349)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:182)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:141)
at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:127)
at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:450)
at org.yaml.snakeyaml.Yaml.load(Yaml.java:393)

Damn. Kotlin data classes have no parameterless constructor by default. They get a parameterless constructor if all parameters have a default value. Naah, I don’t want this. Okay, so no data classes then. Next try:

class UniverseSizeDto {
    var maxGalaxies: Int
    var maxSystems: Int
    var maxPlanets: Int
}

class ResourcesDto {
    var crystal: Int
    var gas: Int
    var energy: Int
}

class StarterPlanetDto {
    var resources: ResourcesDto
}

class ConfigDto {
    var universeSize: UniverseSizeDto
    var starterPlanet: StarterPlanetDto
    var roundTime: Int
}

Argh, doesn’t compile:

Error:(15, 9) Kotlin: Property must be initialized or be abstract
Error:(16, 9) Kotlin: Property must be initialized or be abstract
Error:(17, 9) Kotlin: Property must be initialized or be abstract
Error:(25, 9) Kotlin: Property must be initialized or be abstract
Error:(26, 9) Kotlin: Property must be initialized or be abstract
Error:(27, 9) Kotlin: Property must be initialized or be abstract
Error:(35, 9) Kotlin: Property must be initialized or be abstract
Error:(43, 9) Kotlin: Property must be initialized or be abstract
Error:(44, 9) Kotlin: Property must be initialized or be abstract
Error:(45, 9) Kotlin: Property must be initialized or be abstract

Okay, Kotlin forces us to initialize the variable either in the initializer or in the constructor. After snooping around in the documentation, i found late-initialized properties:

Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

Okay, so some other guy at JetBrains had a similar problem. Let’s give it a try:

class UniverseSizeDto {
    lateinit var maxGalaxies: Int
    lateinit var maxSystems: Int
    lateinit var maxPlanets: Int
}

class ResourcesDto {
    lateinit var crystal: Int
    lateinit var gas: Int
    lateinit var energy: Int
}

class StarterPlanetDto {
    lateinit var resources: ResourcesDto
}

class ConfigDto {
    lateinit var universeSize: UniverseSizeDto
    lateinit var starterPlanet: StarterPlanetDto
    lateinit var roundTime: Int
}

Argh again, doesn’t compile:

Error:(15, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(16, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(17, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(25, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(26, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(27, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(45, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties

Now the compiler is whining about the lateinit bevor the Int fields. Dammit. You can write it this way:

class UniverseSizeDto {
    lateinit var maxGalaxies: Integer
    lateinit var maxSystems: Integer
    lateinit var maxPlanets: Integer
}

class ResourcesDto {
    lateinit var crystal: Integer
    lateinit var gas: Integer
    lateinit var energy: Integer
}

class StarterPlanetDto {
    lateinit var resources: ResourcesDto
}

class ConfigDto {
    lateinit var universeSize: UniverseSizeDto
    lateinit var starterPlanet: StarterPlanetDto
    lateinit var roundTime: Integer
}

Now IntelliJ is shouting at me because I should use Int instead of Integer. At least it compiles and throws no exceptions. But i don’t like warnings either.

Looks like I’m stuck with default values, or I could write my own SnakeYAML constructor. Sigh, default values then.

Now the code looks like this:

data class UniverseSizeDto(var maxGalaxies: Int = 0, var maxSystems: Int = 0, var maxPlanets: Int = 0)

data class ResourcesDto(var crystal: Int = 0, var gas: Int = 0, var energy: Int = 0)

data class StarterPlanetDto(var resources: ResourcesDto = ResourcesDto())

data class ConfigDto(var universeSize: UniverseSizeDto = UniverseSizeDto(), var starterPlanet: StarterPlanetDto = StarterPlanetDto(), var roundTime: Int = 0)

If someone knows a solution to my dilemma, please feel free to contact me.

Update 2016-02-27

I found a pretty solution using Jackson.


Tags: kotlin

Back to posts