-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Support for Simple Map decoding with unnamed toml keys (#246)
### What's done: - Phaze 1: only primitive tables (without nested multiple tables) with no explicit type checking; - The idea is to decode unnamed arguments. For example: ``` [map] a = 1 b = 2 ``` or ``` map = {a=1, b=2} ``` Should be decoded (without knowing and naming explicitly toml keys) to: ``` data class Test { val map: Map<String, Int> } ``` Can be useful for parsing gradle configs.
- Loading branch information
Showing
7 changed files
with
266 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMapDecoder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package com.akuleshov7.ktoml.decoders | ||
|
||
import com.akuleshov7.ktoml.exceptions.UnsupportedDecoderException | ||
import com.akuleshov7.ktoml.tree.nodes.TomlKeyValue | ||
import com.akuleshov7.ktoml.tree.nodes.TomlTable | ||
import kotlinx.serialization.DeserializationStrategy | ||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import kotlinx.serialization.descriptors.SerialDescriptor | ||
import kotlinx.serialization.encoding.CompositeDecoder | ||
import kotlinx.serialization.modules.EmptySerializersModule | ||
import kotlinx.serialization.modules.SerializersModule | ||
|
||
/** | ||
* Sometimes, when you do not know the names of the TOML keys and cannot create a proper class with field names for parsing, | ||
* it can be useful to read and parse TOML tables to a map. This is exactly what this TomlMapDecoder is used for. | ||
* | ||
* @property rootNode toml table that we are trying to decode | ||
* @property decodingElementIndex for iterating over the TOML table we are currently reading | ||
* @property kotlinxIndex for iteration inside the kotlinX loop: [decodeElementIndex -> decodeSerializableElement] | ||
*/ | ||
@ExperimentalSerializationApi | ||
public class TomlMapDecoder( | ||
private val rootNode: TomlTable, | ||
private var decodingElementIndex: Int = 0, | ||
private var kotlinxIndex: Int = 0, | ||
) : TomlAbstractDecoder() { | ||
override val serializersModule: SerializersModule = EmptySerializersModule() | ||
|
||
override fun decodeElementIndex(descriptor: SerialDescriptor): Int { | ||
// we will iterate in the following way: | ||
// for [map] | ||
// a = 1 | ||
// b = 2 | ||
// kotlinxIndex will be 0, 1, 2 ,3 | ||
// and decodingElementIndex will be 0, 1 (as there are only two elements in the table: 'a' and 'b') | ||
decodingElementIndex = kotlinxIndex / 2 | ||
|
||
if (decodingElementIndex == rootNode.children.size) { | ||
return CompositeDecoder.DECODE_DONE | ||
} | ||
|
||
return kotlinxIndex++ | ||
} | ||
|
||
override fun <T> decodeSerializableElement( | ||
descriptor: SerialDescriptor, | ||
index: Int, | ||
deserializer: DeserializationStrategy<T>, | ||
previousValue: T? | ||
): T { | ||
val returnValue = when (val processedNode = rootNode.children[decodingElementIndex]) { | ||
// simple decoding for key-value type | ||
is TomlKeyValue -> processedNode | ||
else -> throw UnsupportedDecoderException( | ||
""" Attempting to decode <$rootNode>; however, custom Map decoders do not currently support nested structures. | ||
Decoding is limited to plain structures only: | ||
[map] | ||
a = 1 | ||
b = 2 | ||
c = "3" | ||
""" | ||
) | ||
} | ||
|
||
return ((if (index % 2 == 0) returnValue.key.toString() else returnValue.value.content)) as T | ||
} | ||
|
||
override fun decodeKeyValue(): TomlKeyValue { | ||
TODO("No need to implement decodeKeyValue for TomlMapDecoder as it is not needed for such primitive decoders") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PlainMapDecoderTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package com.akuleshov7.ktoml.decoders | ||
|
||
import com.akuleshov7.ktoml.Toml | ||
import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException | ||
import com.akuleshov7.ktoml.exceptions.TomlDecodingException | ||
import com.akuleshov7.ktoml.exceptions.UnsupportedDecoderException | ||
|
||
import kotlinx.serialization.decodeFromString | ||
import kotlinx.serialization.encodeToString | ||
import kotlinx.serialization.Serializable | ||
import kotlin.test.* | ||
|
||
class PlainMapDecoderTest { | ||
@Serializable | ||
private data class TestDataMap( | ||
val text: String = "Test", | ||
val map: Map<String, Long>, | ||
val number: Int = 31, | ||
) | ||
|
||
@Test | ||
@Ignore | ||
fun typeErrorsInDecoding() { | ||
val data = """ | ||
text = "Test" | ||
number = 15 | ||
[map] | ||
a = "fff" | ||
b = 2 | ||
c = 3 | ||
number = 31 | ||
e = 4 | ||
""".trimIndent() | ||
|
||
assertFailsWith<TomlDecodingException> { | ||
Toml.decodeFromString<TestDataMap>(data) | ||
} | ||
} | ||
|
||
@Test | ||
fun testMapDecoderPositiveCase() { | ||
var data = """ | ||
text = "Test" | ||
number = 15 | ||
[map] | ||
a = 1 | ||
b = 2 | ||
c = 3 | ||
number = 31 | ||
e = 4 | ||
""".trimIndent() | ||
|
||
assertEquals( | ||
TestDataMap("Test", mapOf("a" to 1, "b" to 2, "c" to 3, "number" to 31, "e" to 4), 15), | ||
Toml.decodeFromString<TestDataMap>(data) | ||
) | ||
|
||
data = """ | ||
text = "Test" | ||
number = 15 | ||
[map] | ||
a = 1 | ||
b = 2 | ||
c = 3 | ||
number = 31 | ||
# e = 4 | ||
""".trimIndent() | ||
|
||
assertEquals( | ||
TestDataMap("Test", mapOf("a" to 1, "b" to 2, "c" to 3, "number" to 31), 15), | ||
Toml.decodeFromString<TestDataMap>(data) | ||
) | ||
|
||
data = """ | ||
[map] | ||
a = 1 | ||
b = 2 | ||
c = 3 | ||
number = 15 | ||
# e = 4 | ||
""".trimIndent() | ||
|
||
assertEquals( | ||
TestDataMap("Test", mapOf("a" to 1, "b" to 2, "c" to 3, "number" to 15), 31), | ||
Toml.decodeFromString<TestDataMap>(data) | ||
) | ||
|
||
|
||
data = """ | ||
map = { a = 1, b = 2, c = 3, number = 15 } | ||
text = "Test" | ||
number = 15 | ||
""".trimIndent() | ||
|
||
assertEquals( | ||
TestDataMap("Test", mapOf("a" to 1, "b" to 2, "c" to 3, "number" to 15), 15), | ||
Toml.decodeFromString<TestDataMap>(data) | ||
) | ||
} | ||
|
||
@Test | ||
fun testMapDecoderNegativeCases() { | ||
var data = """ | ||
a = 1 | ||
b = 1 | ||
c = 1 | ||
text = "Test" | ||
number = 15 | ||
""".trimIndent() | ||
|
||
assertFailsWith<MissingRequiredPropertyException> { | ||
Toml.decodeFromString<TestDataMap>(data) | ||
} | ||
|
||
data = """ | ||
[map] | ||
[map.a] | ||
b = 1 | ||
[map.b] | ||
c = 1 | ||
text = "Test" | ||
number = 15 | ||
""".trimIndent() | ||
|
||
assertFailsWith<UnsupportedDecoderException> { | ||
Toml.decodeFromString<TestDataMap>(data) | ||
} | ||
|
||
data = """ | ||
text = "Test" | ||
number = 15 | ||
""".trimIndent() | ||
|
||
assertFailsWith<MissingRequiredPropertyException> { | ||
Toml.decodeFromString<TestDataMap>(data) | ||
} | ||
} | ||
|
||
@Test | ||
fun testSimpleMapDecoder() { | ||
val data = TestDataMap(text = "text value", number = 7321, map = mapOf("a" to 3, "c" to 4)) | ||
val encoded = Toml.encodeToString(data) | ||
val decoded: TestDataMap = Toml.decodeFromString(encoded) | ||
|
||
assertEquals( | ||
data, | ||
decoded | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters