orchestr7 / ktoml

Kotlin Multiplatform parser and compile-time serializer/deserializer for TOML format (Native, JS, JVM) based on KxS
https://akuleshov7.github.io/ktoml
MIT License
456 stars 25 forks source link

Exception when decoding map #234

Closed jakubgwozdz closed 10 months ago

jakubgwozdz commented 1 year ago

Having this code:

@Serializable
private data class TestData(
    val text: String,
    val map: Map<String, String>,
    val number: Int,
)
fun main() {
    val data = TestData(text = "text value", number = 7321, map = mapOf("a" to "b"))
    val encoded = Toml.encodeToString(data)
    println("=====\n$encoded\n=====")
    val decoded: TestData = Toml.decodeFromString(encoded) // throws MissingRequiredPropertyException
}

The output is this:

=====
text = "text value"
number = 7321

[map]
    a = "b"
=====
Exception in thread "main" com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException: Invalid number of key-value arguments provided in the input for deserialization. Missing required property <0> from class <kotlin.collections.LinkedHashMap> in the input. (In your deserialization class you have declared this field, but it is missing in the input)
    at com.akuleshov7.ktoml.decoders.TomlMainDecoder.checkMissingRequiredProperties(TomlMainDecoder.kt:192)
    at com.akuleshov7.ktoml.decoders.TomlMainDecoder.iterateOverStructure(TomlMainDecoder.kt:243)
    at com.akuleshov7.ktoml.decoders.TomlMainDecoder.beginStructure(TomlMainDecoder.kt:212)

Encoding works perfectly well and toml content is generated without problems, but decoding this content back - well, we're not there yet :)

orchestr7 commented 1 year ago

will be investigated this month

orchestr7 commented 1 year ago

thank you for your report and sorry for a long delay!

y9san9 commented 1 year ago

Still having the issue, is there any workaround? Really need to deserialize libs.versions.toml

orchestr7 commented 1 year ago

@y9san9 sorry for a stupid question, what is the datastucture in toml reflects Maps? Cannot find a simple example.

Any string that you are trying to decode?

y9san9 commented 1 year ago

Your class Toml produces from:

Toml.encodeToString(mapOf("library" to "1.0.0", "kotlin" to "2.0.0")) 

The following:

library = "1.0.0"
kotlin = "2.0.0"

And I want to decode that back, but cannot do that at the moment. All Toml spec is about to be converted to hash tables (probably that's something similar to hash maps)

orchestr7 commented 1 year ago

Hm, but it's a little bit weird actually:

library = "1.0.0"
kotlin = "2.0.0"

is not a Map... It's a number of properties in Toml specification.

To store it to the map, I will need to guess the type somehow and understand that it is really a Map, but not a property, that user forgot to provide (due to his manual bug).

y9san9 commented 1 year ago

So how could I deserialize that?

And also that:

coreKtx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }

All the solutions I found did that using Map

y9san9 commented 1 year ago

(I don't know the exact names, I want to deserialize arbitrary amount of such rows)

orchestr7 commented 1 year ago

So how could I deserialize that?

And also that:

coreKtx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }

All the solutions I found did that using Map

hm, let me look into the spec of TOML

orchestr7 commented 1 year ago

So how could I deserialize that?

And also that:

coreKtx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }

All the solutions I found did that using Map

According to the spec, it's Inline Table https://toml.io/en/v1.0.0#inline-table and we should be able to decode it 🤔

y9san9 commented 1 year ago

A list of inline tables? If so, that doesn't work either.

@Serializable
data class Libraries(
    val libraries: List<@TomlInlineTable Library>
) {
    @Serializable
    data class Library(val module: String, val version: String)
}

How do I get coreKtx with such setup?

y9san9 commented 1 year ago

The intuitive approach: to add pair name -> inline table I used the Map type, which is also being encoded in such structure I needed, but is not being decoded back

orchestr7 commented 1 year ago

A list of inline tables?

I think that this might help: https://github.com/akuleshov7/ktoml#how-ktoml-works-examples

We actually had a test and an example for such structures: gradle-libs-like-property = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

your class:
    @SerialName("gradle-libs-like-property")
    val kotlinJvm: GradlePlugin

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

But actually I do not like the behaviour when we create a map on serialization and then have ambiguity on the deserialisation with the same class. I understood the problem, but do Inline Tables will help you now in your particular case?

y9san9 commented 1 year ago

Sorry, as I mentioned earlier that I don't know what names of the libraries will be. I want to parse them and only after that I could tell the names.

y9san9 commented 1 year ago

In Json when you don't know class layout you would also use Map<String, JsonElement>

orchestr7 commented 1 year ago

Sorry, as I mentioned earlier that I don't know what names of the libraries will be. I want to parse them and only after that I could tell the names.

Got it, so you need to parse properties without explicit naming, and you need to decode it to some map of strings. I do not have an idea of how to support it now, as it is violating TOML spec, but I will think...

y9san9 commented 1 year ago

Why is it violating TOML specs? It's how Version Catalogs work :)

orchestr7 commented 1 year ago

Why is it violating TOML specs? It's how Version Catalogs work :)

Yes, I mean that the idea of TOML is that you know the names of the properties. That's why this structure:

a = 1

is a NAMED key-value property https://toml.io/en/v1.0.0#keyvalue-pair

It will be a different story to decode the following code:

a = 1
b = 2

to a Map("a" to "1", "b" to "2"), because the original idea is about named keys with typed values:

val a: Int
val b: Int
orchestr7 commented 1 year ago

BUT! Ktoml itself has multiple extensions to the original idea of TOML, so probably we can think of something we can do here. Especially when we encode these values to a Map. Need to think...

orchestr7 commented 11 months ago

Initial implementation is done for simple map: https://github.com/akuleshov7/ktoml/pull/246 (phase 1) @jakubgwozdz @y9san9

Phase 2 with nested tables: WIP

orchestr7 commented 10 months ago

Nested Maps (nested tables) done in #252

orchestr7 commented 10 months ago

Released in 0.5.1