Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the support for empty lists (bugfix#77) #81

Merged
merged 1 commit into from
Nov 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 34 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ Toml(
// allow/prohibit unknown names during the deserialization, default false
ignoreUnknownNames = false,
// allow/prohibit empty values like "a = # comment", default true
emptyValuesAllowed = true
emptyValuesAllowed = true,
// allow/prohibit escaping of single quotes in literal strings, default true
escapedQuotesInLiteralStringsAllowed = true
)
).decodeFromString<MyClass>(
tomlString
Expand All @@ -199,14 +201,17 @@ The following example:
someBooleanProperty = true

[table1]
property1 = 5
# it can be null or nil, but don't forget to mark it with '?' in the codes
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 = "my name"
configurationList = ["a", "b", "c"]
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;
Expand All @@ -218,34 +223,37 @@ otherNumber = 5.56
can be deserialized to `MyClass`:
```kotlin
@Serializable
data class MyClass(
val someBooleanProperty: Boolean,
val table1: Table1,
val table2: Table2
)
data class MyClass(val someBooleanProperty: Boolean, val table1: Table1, val table2: Table2)

@Serializable
data class Table1(val property1: Int, val property2: Int)
@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: Int,
@SerialName("akuleshov7.com")
val inlineTable: InlineTable,
val otherNumber: Double
)
@Serializable
data class Table2(
val someNumber: Long,
@SerialName("akuleshov7.com")
val inlineTable: InlineTable,
val otherNumber: Double
)

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

with the following code:
```kotlin
Toml.decodeFromString<MyClass>(/* toml string */)
Toml.decodeFromString<MyClass>(/* your toml string */)
```

Translation of the example above to json-terminology:
Expand All @@ -267,4 +275,4 @@ Translation of the example above to json-terminology:
}
```

You can check how this example works in [ReadMeExampleTest](ktoml-core/src/commonTest/kotlin/decoder/ReadMeExampleTest.kt).
:heavy_exclamation_mark: You can check how this example works in [ReadMeExampleTest](ktoml-core/src/commonTest/kotlin/decoder/ReadMeExampleTest.kt).
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repositories {
}

dependencies {
implementation("org.cqfn.diktat:diktat-gradle-plugin:1.0.0-rc.2")
implementation("org.cqfn.diktat:diktat-gradle-plugin:1.0.0-rc.3")
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0")
implementation("io.github.gradle-nexus:publish-plugin:1.1.0")
implementation("org.ajoberstar.reckon:reckon-gradle:0.13.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package com.akuleshov7.ktoml
/**
* @property ignoreUnknownNames - a control to allow/prohibit unknown names during the deserialization
* @property emptyValuesAllowed - a control to allow/prohibit empty values: a = # comment
* @property escapedQuotesInLiteralStringsAllowed - a control to allow/prohibit escaping of single quotes in literal strings
*/
public data class KtomlConf(
val ignoreUnknownNames: Boolean = false,
val emptyValuesAllowed: Boolean = true,
val escapedQuotesInLiteralStringsAllowed: Boolean = false
val escapedQuotesInLiteralStringsAllowed: Boolean = true
)
18 changes: 12 additions & 6 deletions ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.akuleshov7.ktoml

import com.akuleshov7.ktoml.decoders.TomlDecoder
import com.akuleshov7.ktoml.decoders.TomlMainDecoder
import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException
import com.akuleshov7.ktoml.parsers.TomlParser
import com.akuleshov7.ktoml.parsers.node.TomlFile
Expand Down Expand Up @@ -35,7 +35,7 @@ public open class Toml(
*/
override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
val parsedToml = tomlParser.parseString(string)
return TomlDecoder.decode(deserializer, parsedToml, config)
return TomlMainDecoder.decode(deserializer, parsedToml, config)
}

override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
Expand All @@ -49,11 +49,15 @@ public open class Toml(
*
* @param toml list with strings in toml format
* @param deserializer deserialization strategy
* @param ktomlConf
* @return deserialized object of type T
*/
public fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, toml: List<String>, ktomlConf: KtomlConf): T {
public fun <T> decodeFromString(
deserializer: DeserializationStrategy<T>,
toml: List<String>,
ktomlConf: KtomlConf): T {
val parsedToml = tomlParser.parseStringsToTomlTree(toml, ktomlConf)
return TomlDecoder.decode(deserializer, parsedToml, config)
return TomlMainDecoder.decode(deserializer, parsedToml, config)
}

/**
Expand All @@ -67,6 +71,7 @@ public open class Toml(
* @param deserializer deserialization strategy
* @param toml request-string in toml format with '\n' or '\r\n' separation
* @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d)
* @param ktomlConf
* @return deserialized object of type T
*/
public fun <T> partiallyDecodeFromString(
Expand All @@ -76,7 +81,7 @@ public open class Toml(
ktomlConf: KtomlConf = KtomlConf()
): T {
val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, ktomlConf, TomlParser::parseString)
return TomlDecoder.decode(deserializer, fakeFileNode, config)
return TomlMainDecoder.decode(deserializer, fakeFileNode, config)
}

/**
Expand All @@ -90,6 +95,7 @@ public open class Toml(
* @param deserializer deserialization strategy
* @param toml list of strings with toml input
* @param tomlTableName fully qualified name of the toml table (it should be the full name - a.b.c.d)
* @param ktomlConf
* @return deserialized object of type T
*/
public fun <T> partiallyDecodeFromString(
Expand All @@ -104,7 +110,7 @@ public open class Toml(
ktomlConf,
TomlParser::parseString,
)
return TomlDecoder.decode(deserializer, fakeFileNode, config)
return TomlMainDecoder.decode(deserializer, fakeFileNode, config)
}

// ================== other ===============
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.akuleshov7.ktoml.decoders

import com.akuleshov7.ktoml.exceptions.IllegalTomlTypeException
import com.akuleshov7.ktoml.exceptions.TomlCastException
import com.akuleshov7.ktoml.parsers.node.TomlKeyValue
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encoding.AbstractDecoder

/**
* Abstract Decoder for TOML format that is inherited by each and every decoder in this project.
* It serves one aim: to define decoders for primitive types that are allowed in TOML.
*/
@ExperimentalSerializationApi
public abstract class TomlAbstractDecoder : AbstractDecoder() {
// Invalid Toml primitive types, we will simply throw an error for them
override fun decodeByte(): Byte = invalidType("Byte", "Long")
override fun decodeShort(): Short = invalidType("Short", "Long")
override fun decodeInt(): Int = invalidType("Int", "Long")
override fun decodeFloat(): Float = invalidType("Float", "Double")
override fun decodeChar(): Char = invalidType("Char", "String")

// Valid Toml types that should be properly decoded
override fun decodeBoolean(): Boolean = decodePrimitiveType()
override fun decodeLong(): Long = decodePrimitiveType()
override fun decodeDouble(): Double = decodePrimitiveType()
override fun decodeString(): String = decodePrimitiveType()

internal abstract fun decodeKeyValue(): TomlKeyValue

private fun invalidType(typeName: String, requiredType: String): Nothing {
val keyValue = decodeKeyValue()
throw IllegalTomlTypeException(
"<$typeName> type is not allowed by toml specification," +
" use <$requiredType> instead" +
" (field = ${keyValue.key.content}; value = ${keyValue.value.content})", keyValue.lineNo
)
}

private inline fun <reified T> decodePrimitiveType(): T {
val keyValue = decodeKeyValue()
try {
return keyValue.value.content as T
} catch (e: ClassCastException) {
throw TomlCastException(
"Cannot decode the key [${keyValue.key.content}] with the value [${keyValue.value.content}]" +
" with the provided type [${T::class}]. Please check the type in your Serializable class or it's nullability",
keyValue.lineNo
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
package com.akuleshov7.ktoml.decoders

import com.akuleshov7.ktoml.KtomlConf
import com.akuleshov7.ktoml.parsers.node.TomlKeyValueList
import com.akuleshov7.ktoml.parsers.node.TomlKeyValue
import com.akuleshov7.ktoml.parsers.node.TomlKeyValueArray
import com.akuleshov7.ktoml.parsers.node.TomlKeyValuePrimitive
import com.akuleshov7.ktoml.parsers.node.TomlNull
import com.akuleshov7.ktoml.parsers.node.TomlValue
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.AbstractDecoder
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule

/**
* @property rootNode
* @property config
* @property ktomlConf
*/
@ExperimentalSerializationApi
@Suppress("UNCHECKED_CAST")
public class TomlListDecoder(
public val rootNode: TomlKeyValueList,
public val config: KtomlConf,
) : AbstractDecoder() {
public class TomlArrayDecoder(
private val rootNode: TomlKeyValueArray,
private val ktomlConf: KtomlConf,
) : TomlAbstractDecoder() {
private var nextElementIndex = 0
private val list = rootNode.value.content as List<TomlValue>
override val serializersModule: SerializersModule = EmptySerializersModule
private lateinit var currentElementDecoder: TomlScalarDecoder
private lateinit var currentElementDecoder: TomlPrimitiveDecoder
private lateinit var currentPrimitiveElementOfArray: TomlValue

private fun haveStartedReadingElements() = nextElementIndex > 0

Expand All @@ -33,8 +36,18 @@ public class TomlListDecoder(
if (nextElementIndex == list.size) {
return CompositeDecoder.DECODE_DONE
}
currentElementDecoder = TomlScalarDecoder(
list[nextElementIndex]

currentPrimitiveElementOfArray = list[nextElementIndex]

currentElementDecoder = TomlPrimitiveDecoder(
// a small hack that creates a PrimitiveKeyValue node that is used in the decoder
TomlKeyValuePrimitive(
rootNode.key,
currentPrimitiveElementOfArray,
rootNode.lineNo,
rootNode.key.content,
ktomlConf
)
)
return nextElementIndex++
}
Expand All @@ -46,6 +59,9 @@ public class TomlListDecoder(
return super.beginStructure(descriptor)
}

override fun decodeKeyValue(): TomlKeyValue = throw NotImplementedError("Method `decodeKeyValue`" +
" should never be called for TomlListDecoder, it should use ")

override fun decodeString(): String = currentElementDecoder.decodeString()
override fun decodeInt(): Int = currentElementDecoder.decodeInt()
override fun decodeLong(): Long = currentElementDecoder.decodeLong()
Expand All @@ -56,4 +72,7 @@ public class TomlListDecoder(
override fun decodeBoolean(): Boolean = currentElementDecoder.decodeBoolean()
override fun decodeChar(): Char = currentElementDecoder.decodeChar()
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = currentElementDecoder.decodeEnum(enumDescriptor)

// this should be applied to [currentPrimitiveElementOfArray] and not to the [rootNode], because
override fun decodeNotNullMark(): Boolean = currentPrimitiveElementOfArray !is TomlNull
}
Loading