Skip to content
forked from orchestr7/ktoml

Multiplatform TOML parser and compile-time serializer/deserializer for Kotlin (Native, JS, JVM) based on KxS

License

Notifications You must be signed in to change notification settings

CyberFlameGO/ktoml

Repository files navigation

Releases Maven Central License Build and test Lines of code Hits-of-Code GitHub repo size codebeat badge maintainability Run deteKT Run diKTat

Fully Native and Multiplatform Kotlin serialization library for serialization/deserialization of toml format. Uses native kotlinx.serialization, provided by Kotlin. This library contains no Java code and no Java dependencies. We believe that TOML is actually the most readable and user-friendly configuration file format. So we decided to support this format for the kotlinx serialization library.

Contribution

As this young and big project is needed by the Kotlin community, we need your help. We will be glad if you will test ktoml or contribute to this project. In case you don't have much time for this - at least spend 5 seconds to give us a star to attract other contributors!

Thanks! 🙏 🥳

Acknowledgement

Special thanks to those awesome developers who give us great suggestions, help us to maintain and improve this project: @NightEule5, @bishiboosh, @Peanuuutz, @petertrr, @nulls, @Olivki and @edrd-f.

Supported platforms

All the code is written in Kotlin common module. This means that it can be built for each and every Kotlin native platform. However, to reduce the scope, ktoml now supports only the following platforms:

  • jvm
  • mingwx64
  • linuxx64
  • macosx64
  • ios
  • js (obviously only for ktoml-core!). Note, that js(LEGACY) is not supported

Other platforms could be added later on the demand (just create a corresponding issue) or easily built by users on their machines.

🌐 ktoml supports Kotlin 1.7

Current limitations

❗ Please note, that TOML standard does not define Java-like types: Char, Short, etc. You can check types that are supported in TOML here. We will support all Kotlin primitive types in the future with the non-strict configuration of ktoml, but now only String, Long, Double and Boolean are supported from the list of Kotlin primitives.

General
We are still developing and testing this library, so it has several limitations:
✅ deserialization (with some parsing limitations)
✅ serialization (with tree-related limitations)

Parsing
✅ Table sections (single and dotted)
✅ Key-value pairs (single and dotted)
✅ Integer type
✅ Float type
✅ String type
✅ Float type
✅ Boolean type
✅ Simple Arrays
✅ Comments
✅ Literal Strings
✅ Inline Tables
✅ Offset Date-Time (to Instant of kotlinx-datetime)
✅ Local Date-Time (to LocalDateTime of kotlinx-datetime)
✅ Local Date (to LocalDate of kotlinx-datetime)
❌ Arrays: nested; multiline; of Different Types
❌ Multiline Strings
❌ Nested Inline Tables
❌ Array of Tables
❌ Inline Array of Tables
❌ Local Time

Dependency

The library is hosted on the Maven Central. To import ktoml library you need to add following dependencies to your code:

Maven
<dependency>
  <groupId>com.akuleshov7</groupId>
  <artifactId>ktoml-core</artifactId>
  <version>0.3.0</version>
</dependency>
<dependency>
  <groupId>com.akuleshov7</groupId>
  <artifactId>ktoml-file</artifactId>
  <version>0.3.0</version>
</dependency>
Gradle Groovy
implementation 'com.akuleshov7:ktoml-core:0.3.0'
implementation 'com.akuleshov7:ktoml-file:0.3.0'
Gradle Kotlin
implementation("com.akuleshov7:ktoml-core:0.3.0")
implementation("com.akuleshov7:ktoml-file:0.3.0")

How to use

❗ as TOML is a foremost language for config files, we have also supported the deserialization from file. However, we are using okio to read the file, so it will be added as a dependency to your project if you will import ktoml-file. For basic scenarios of decoding strings you can simple use ktoml-core.

❗ don't forget to add the serialization plugin kotlin("plugin.serialization") to your project. Otherwise, @Serialization annotation won't work properly.

Deserialization:

Straight-forward deserialization
// add extensions from 'kotlinx' lib to your project:
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.serializer
// add com.akuleshov7:ktoml-core to your project:
import com.akuleshov7.ktoml.deserialize

@Serializable
data class MyClass(/* your fields */)

// to deserialize toml input in a string format (separated by newlines '\n')
// no need to provide serializer() explicitly if you will use extension method from
// <kotlinx.serialization.decodeFromString>
val resultFromString = Toml.decodeFromString<MyClass>(/* string with a toml input */)
val resultFromList = Toml.decodeFromString<MyClass>(serializer(), /* list with lines of strings with a toml input */)
Partial deserialization

Partial Deserialization can be useful when you would like to deserialize only one single table and you do not want to reproduce whole object structure in your code.

// If you need to deserialize only some part of the toml - provide the full name of the toml table. 
// The deserializer will work only with this table and it's children.
// For example if you have the following toml, but you want only to decode [c.d.e.f] table: 
// [a]
//   b = 1
// [c.d.e.f]
//   d = "5"

val result = Toml.partiallyDecodeFromString<MyClassOnlyForTable>(serializer(), /* string with a toml input */, "c.d.e.f")
val result = Toml.partiallyDecodeFromString<MyClassOnlyForTable>(serializer(), /* list with toml strings */, "c.d.e.f")
Toml File deserialization
// add com.akuleshov7:ktoml-file to your project
import com.akuleshov7.ktoml.file

val resultFromString = TomlFileReader.decodeFromFile<MyClass>(serializer(), /* file path to toml file */)
val resultFromList = TomlFileReader.partiallyDecodeFromFile<MyClass>(serializer(),  /* file path to toml file */, /* table name */)

Serialization:

Straight-forward serialization
// add extensions from 'kotlinx' lib to your project:
import kotlinx.serialization.encodeFromString
// add com.akuleshov7:ktoml-core to your project:
import com.akuleshov7.ktoml.Toml

@Serializable
data class MyClass(/* your fields */)

val toml = Toml.decodeFromString(MyClass(/* ... */))
Toml File serialization
// add com.akuleshov7:ktoml-file to your project
import com.akuleshov7.ktoml.file.TomlFileWriter

TomlFileWriter.encodeToFile<MyClass>(serializer(), /* file path to toml file */)

Parser to AST:

Simple parser
import com.akuleshov7.ktoml.parsers.TomlParser
import com.akuleshov7.ktoml.TomlConfig
/* ========= */
var tomlAST = TomlParser(TomlInputConfig()).parseStringsToTomlTree(/* list with toml strings */)
tomlAST = TomlParser(TomlInputConfig()).parseString(/* the string that you want to parse */)
tomlAST.prettyPrint()

Configuration

Ktoml parsing and deserialization was made configurable to fit all the requirements from users. We have created a special configuration class that can be passed to the decoder method:

Toml(
    inputConfig = TomlInputConfig(
        // allow/prohibit unknown names during the deserialization, default false
        ignoreUnknownNames = false,
        // allow/prohibit empty values like "a = # comment", default true
        allowEmptyValues = true,
        // allow/prohibit null values like "a = null", default true
        allowNullValues = true,
        // allow/prohibit escaping of single quotes in literal strings, default true
        allowEscapedQuotesInLiteralStrings = true,
        // allow/prohibit processing of empty toml, if false - throws an InternalDecodingException exception, default is true
        allowEmptyToml = true,
    ),
    outputConfig = TomlOutputConfig(
        // indentation symbols for serialization, default 4 spaces
        indentation = Indentation.FOUR_SPACES,
    )
).decodeFromString<MyClass>(
    tomlString
)

Exceptions

Ktoml will produce different exceptions in case of the invalid input. Please note, that some of strict checks can be enabled or disabled (please see Configuration section of this readme). We intentionally made only two parental sealed exceptions public: TomlDecodingException and TomlEncodingException - you can catch them in your code. All other exceptions inherit one of these two and will not be public.

How ktoml works: examples

❗ You can check how below examples work in decoding ReadMeExampleTest and encoding ReadMeExampleTest.

Deserialization The following example:
someBooleanProperty = true
# inline tables in gradle 'libs.versions.toml' notation
gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

[table1]
# it can be null or nil, but don't forget to mark it with '?' in the code
# keep in mind, that null is prohibited by TOML spec, but it is very important in Kotlin, so we supported it
# see allowNullValues for a more strict enforcement of the TOML spec
property1 = null
property2 = 6
# check property3 in Table1 below. As it has the default value, it is not required and can be not provided 
 
[table2]
someNumber = 5
   [table2."akuleshov7.com"]
       name = 'this is a "literal" string'
       # empty lists are also supported
       configurationList = ["a",  "b",  "c", null]

# such redeclaration of table2
# is prohibited in toml specification;
# but ktoml is allowing it in non-strict mode: 
[table2]       
otherNumber = 5.56

can be deserialized to MyClass:

@Serializable
data class MyClass(
    val someBooleanProperty: Boolean,
    val table1: Table1,
    val table2: Table2,
   @SerialName("gradle-libs-like-property")
   val kotlinJvm: GradlePlugin
)

@Serializable
data class Table1(
    // nullable values, from toml you can pass null/nil/empty value to this kind of a field
    val property1: Long?,
    // please note, that according to the specification of toml integer values should be represented with Long
    val property2: Long,
    // no need to pass this value as it has the default value and is NOT REQUIRED
    val property3: Long = 5
)

@Serializable
data class Table2(
    val someNumber: Long,
    @SerialName("akuleshov7.com")
    val inlineTable: InlineTable,
    val otherNumber: Double
)

@Serializable
data class GradlePlugin(val id: String, val version: Version)

@Serializable
data class Version(val ref: String)

with the following code:

Toml.decodeFromString<MyClass>(/* your toml string */)

Translation of the example above to json-terminology:

{
  "someBooleanProperty": true,
  "table1": {
    "property1": 5,
    "property2": 5
  },
  "table2": {
    "someNumber": 5,
    "akuleshov7.com": {
      "name": "my name",
      "configurationList": [
        "a",
        "b",
        "c"
      ],
      "otherNumber": 5.56
    }
  },
  "gradle-libs-like-property": {
    "id": "org.jetbrains.kotlin.jvm",
    "version": {
      "ref": "kotlin"
    }
  }
}
Serialization The following example from above:
someBooleanProperty = true
gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

# Comments can be added
# More comments can also be added
[table1]
    property1 = null # At the end of lines too
    property2 = 6

[table2]
    someNumber = 5

    # Properties always appear before sub-tables, tables aren't redeclared
    otherNumber = 5.56
    [table2."akuleshov7.com"]
        name = 'this is a "literal" string'
        configurationList = ["a",  "b",  "c", null]

can be serialized from MyClass:

@Serializable
data class MyClass(
    val someBooleanProperty: Boolean,
    @TomlComments(
        "Comments can be added",
        "More comments can also be added"
    )
    val table1: Table1,
    val table2: Table2,
   @SerialName("gradle-libs-like-property")
   val kotlinJvm: GradlePlugin
)

@Serializable
data class Table1(
    @TomlComments(inline = "At the end of lines too")
    // nullable values, represented as "null" in toml. For more strict behavior,
    // null values can be ignored with the ignoreNullValues config property.
    val property1: Long?,
    // please note, that according to the specification of toml integer values should be represented with Long
    val property2: Long,
    // Default values can be ignored with the ignoreDefaultValues config property.
    val property3: Long = 5
)

@Serializable
data class Table2(
    // Integers can be formatted in hex, binary, etc. Currently only decimal is
    // supported.
    @TomlInteger(IntegerRepresentation.DECIMAL)
    val someNumber: Long,
    @SerialName("akuleshov7.com")
    @TomlInlineTable // Can be on the property
    val inlineTable: InlineTable,
    @TomlComments(
        "Properties always appear before sub-tables, tables aren't redeclared"
    )
    val otherNumber: Double
)

@Serializable
data class InlineTable(
    @TomlLiteral
    val name: String,
    @SerialName("configurationList")
    val overriddenName: List<String?>
)

@Serializable
@TomlInlineTable // ...or the class
data class GradlePlugin(
    val id: String,
    // version is "collapsed": single member inline tables become dotted pairs.
    val version: Version
)

@Serializable
@TomlInlineTable
data class Version(val ref: String)

with the following code:

Toml.decodeFromString<MyClass>(/* your toml string */)

About

Multiplatform TOML parser and compile-time serializer/deserializer for Kotlin (Native, JS, JVM) based on KxS

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages

  • Kotlin 99.9%
  • Shell 0.1%