Skip to content

Multiplatform TOML library with builtin support for kotlinx.serialization

License

Notifications You must be signed in to change notification settings

Peanuuutz/tomlkt

Repository files navigation

tomlkt

Maven Central License

Lightweight and easy to use kotlinx.serialization plugin for TOML serialization and deserialization.

If you find any problem along usage, please raise an issue. 😉

Setup

Gradle Kotlin (build.gradle.kts)
repositories {
    mavenCentral()
}

dependencies {
    implementation("net.peanuuutz.tomlkt:tomlkt:0.3.4")
}
Gradle Groovy (build.gradle)
repositories {
    mavenCentral()
}

dependencies {
    implementation "net.peanuuutz.tomlkt:tomlkt:0.3.4"
}
Maven (.pom)
<dependency>
  <groupId>net.peanuuutz.tomlkt</groupId>
  <artifactId>tomlkt-jvm</artifactId>
  <version>0.3.4</version>
</dependency>

Note: If your project is Kotlin Multiplatform, you can simply add this into commonMain dependencies.

Quick Start

Write some config:

name = "Peanuuutz"

[account]
username = "Peanuuutz"
password = "123456"

Write some code:

@Serializable
data class User(
    val name: String,
    val account: Account?
)

@Serializable
data class Account(
    val username: String,
    val password: String
)

fun main() {
    // Here we use JVM.
    val tomlString = Paths.get("...").readText()
    // Either is OK, but to explicitly pass a serializer is faster.
    val user = Toml.decodeFromString(User.serializer(), tomlString)
    val user = Toml.decodeFromString<User>(tomlString)
    // That's it!

    // By the way if you need some configuration.
    val toml = Toml {
        ignoreUnknownKeys = true
    }
    // Use toml to apply the change.

    // Serialization.
    val anotherUser = User("Anonymous", null)
    // Again, better to explicitly pass a serializer.
    val config = Toml.encodeToString(User.serializer(), anotherUser)
    Paths.get("...").writeText(config)
    // Done.
}

Features

TOML format Serialization Deserialization
Comment ✔️ ✔️
Key ✔️ ✔️
String ✔️ ✔️
Integer ✔️ ✔️
Float ✔️ ✔️
Boolean ✔️ ✔️
Date Time ✔️ ✔️
Array ✔️ ✔️
Table ✔️ ✔️❔
Inline Table ✔️ ✔️
Array of Tables ✔️ ✔️

Comment

Implemented as an annotation @TomlComment on properties:

class IntData(
    @TomlComment("""
        An integer,
        but is decoded into Long originally
    """)
    val int: Int
)
IntData(10086)

The code above will be encoded into:

# An integer,
# but is decoded into Long originally
int = 10086

String

Basic strings are encoded as "<content>". For multilines and literals, put an annotation as below:

class MultilineStringData(
    @TomlMultilineString
    val multilineString: String
)
MultilineStringData("""
    Do, a deer, a female deer.
    Re, a drop of golden sun.
""".trimIndent())

class LiteralStringData(
    @TomlLiteralString
    val literalString: String
)
LiteralStringData("C:\\Users\\<User>\\.m2\\repositories")

The code above will be encoded into:

multilineString = """
Do, a deer, a female deer.
Re, a drop of golden sun."""

literalString = 'C:\Users\<User>\.m2\repositories'

You can use both annotations to get multiline literal string.

Date Time

TOML supports several date time formats, so does tomlkt. tomlkt declares TomlLocalDateTime, TomlOffsetDateTime, TomlLocalDate, TomlLocalTime as expect types with builtin support for serialization (@Serializable).

The mapping of these expect types are as follows:

tomlkt java.time kotlinx.datetime
TomlLocalDateTime LocalDateTime LocalDateTime
TomlOffsetDateTime OffsetDateTime Instant
TomlLocalDate LocalDate LocalDate
TomlLocalTime LocalTime LocalTime

TomlLiteral is the default intermediate representation of a date time. For conversion, simply use TomlLiteral(TomlLocalDateTime) to create a TomlLiteral from a TomlLocalDateTime (true for other types), and TomlLiteral.toLocalDateTime() for the other way.

If you'd like to provide a custom serializer, use NativeLocalDateTime and the like as raw types.

Table

❔: There's an internal issue. When you define super-table before the sub-table:

[x]
[x.y]

It will be successfully parsed, but if you define after that:

[x.y]
[x]

It will throw net.peanuuutz.tomlkt.internal.ConflictEntryException. Due to the reading process of TomlFileParser, each time a table head is parsed, the path will be immediately put into the whole tree, and meanwhile be checked if is already defined.

Better to keep super-table first!

Extra features

The working process of tomlkt:

  • Serialization: Model / TomlElement → (TomlFileEncoder) → File(String); Model → (TomlElementEncoder) → TomlElement.
  • Deserialization: File(String) → (TomlFileParser) → TomlElement → (TomlElementDecoder) → Model.

As you see, if you already have a TOML file, you can have no model class, but still gain access to every entry with the help of TomlElement.

Note: Due to no context of values in TomlTable(see TomlElement.kt), all of those are encoded as inline(meaning you can't get the same serialized structure between model class and TomlTable).

For other information, view API docs.