Repository: orchestr7/ktoml (kotlin)

Issue:
encodeToString produces wrong indentation for case when there are nested List among the tree
```kotlin
class NestedListsTest {
    @Serializable
    data class Wrapper(
        val elements: List<Element>
    )

    @Serializable
    data class Element(
        val name: String,
        val subElements: List<SubElement>
    )

    @Serializable
    data class SubElement(
        val name: String,
        val description: String,
    )

    @Test
    fun test() {
        val data = Wrapper(
            elements = listOf(
                Element(
                    name = "element 1",
                    subElements = listOf(
                        SubElement("1.1", "d1.1"),
                        SubElement("1.2", "d1.2"),
                    )
                ),
            )
        )
        val serialized = Toml.encodeToString(data)

        Assert.assertEquals(
            serialized,
            """
            [[elements]]
                name = "element 1"

                [[elements.subElements]]
                    name = "1.1"
                    description = "d1.1"

                [[elements.subElements]]
                    name = "1.2"
                    description = "d1.2"
            """.trimIndent()
        )

        val deserialized = Toml.decodeFromString<Wrapper>(serialized)

        Assert.assertEquals(data, deserialized)
    }
}
```

`com.akuleshov7:ktoml-core:0.5.1` yields

```toml
[[elements]]
    name = "element 1"

    [[elements.subElements]]
        name = "1.1"
        description = "d1.1"

[[elements.subElements]]
        name = "1.2"
        description = "d1.2"
```

The generated TOML does not parse. It would be nice if the error message included the problematic field name or something like that. Currently the error message provides little to no clue on why the parsing went wrong:

```
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.ArrayList> in the input. (In your deserialization class you have declared this field, but it is missing in the input)
	at app//com.akuleshov7.ktoml.decoders.TomlMainDecoder.checkMissingRequiredProperties(TomlMainDecoder.kt:200)
	at app//com.akuleshov7.ktoml.decoders.TomlMainDecoder.iterateOverTomlStructure(TomlMainDecoder.kt:258)
	at app//com.akuleshov7.ktoml.decoders.TomlMainDecoder.beginStructure(TomlMainDecoder.kt:220)
	at app//kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:29)
	at app//kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
	at app//kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:257)
	at app//kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
	at app//com.akuleshov7.ktoml.decoders.TomlAbstractDecoder.decodeSerializableValue(TomlAbstractDecoder.kt:96)
	at app//kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
	at app//kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
	at app//com.example.NestedListsTest$Wrapper$$serializer.deserialize(TomlTest.kt:11)
	at app//com.example.NestedListsTest$Wrapper$$serializer.deserialize(TomlTest.kt:11)
	at app//kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:257)
	at app//kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
	at app//com.akuleshov7.ktoml.decoders.TomlAbstractDecoder.decodeSerializableValue(TomlAbstractDecoder.kt:96)
	at app//com.akuleshov7.ktoml.decoders.TomlMainDecoder$Companion.decode(TomlMainDecoder.kt:293)
	at app//com.akuleshov7.ktoml.Toml.decodeFromString(Toml.kt:47)
	at app//com.example.NestedListsTest.test(TomlTest.kt:66)
```

Pull request:
Fix #254 

There was a bug that only the first element in array of tables had correct indent for header. Now, only TomlTable is responsible for emitting indents for tables and it works :)

Patch:
diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt
index 832de9ce..dbf3f4fc 100644
--- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt
+++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/TomlNode.kt
@@ -244,8 +244,7 @@ public sealed class TomlNode(
         children.forEachIndexed { i, child ->
             writeChildComments(child)
 
-            // Prevent double indentation on dotted table elements.
-            if (child !is TomlTable || (!child.isSynthetic && child.getFirstChild() !is TomlTable)) {
+            if (child !is TomlTable) {
                 emitIndent()
             }
 
diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt
index 8c4d9369..a5304a15 100644
--- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt
+++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/tables/TomlTable.kt
@@ -71,6 +71,7 @@ public class TomlTable(
         val firstChild = children.firstOrNull() ?: return
 
         if (isExplicit(firstChild) && type == TableType.PRIMITIVE) {
+            emitter.emitIndent()
             emitter.writeHeader()
 
             if (inlineComment.isNotEmpty()) {
@@ -104,9 +105,7 @@ public class TomlTable(
         config: TomlOutputConfig
     ) {
         if (child is TomlArrayOfTablesElement) {
-            if (parent !is TomlArrayOfTablesElement) {
-                emitIndent()
-            }
+            emitIndent()
 
             writeChildComments(child)
             writeHeader()
@@ -137,13 +136,6 @@ public class TomlTable(
         }
     }
 
-    private fun shouldEmitIndentBeforeTomlTable(
-        child: TomlNode,
-    ): Boolean = child !is TomlTable ||
-            (child.type == TableType.PRIMITIVE &&
-                    !child.isSynthetic &&
-                    child.getFirstChild() !is TomlTable)
-
     private fun TomlEmitter.writePrimitiveChild(
         index: Int,
         prevChild: TomlNode?,
@@ -167,7 +159,8 @@ public class TomlTable(
             indent()
         }
 
-        if (child !is TomlStubEmptyNode && shouldEmitIndentBeforeTomlTable(child)) {
+        // Prevent double indentation on dotted table elements (when child is TomlTable)
+        if (child !is TomlStubEmptyNode && child !is TomlTable) {
             emitIndent()
         }
 


Test patch:
diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt
index 508a4717..4bb2537d 100644
--- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt
+++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/encoders/ArrayOfTablesEncoderTest.kt
@@ -106,6 +106,137 @@ class ArrayOfTablesEncoderTest {
         )
     }
 
+    @Test
+    fun encodeSubSubTablesWithMultipleValues() {
+        @Serializable
+        data class SubSubElement(
+            val name: String,
+            val description: String,
+        )
+        @Serializable
+        data class SubElement(
+            val name: String,
+            val description: String,
+            val subSubElements: List<SubSubElement>,
+        )
+        @Serializable
+        data class Element(
+            val name: String,
+            val subElements: List<SubElement>
+        )
+        @Serializable
+        data class Wrapper(
+            val elements: List<Element>
+        )
+
+
+        val data = Wrapper(
+            elements = listOf(
+                Element(
+                    name = "element 1",
+                    subElements = listOf(
+                        SubElement(
+                            "1.1", "d1.1",
+                            subSubElements =
+                                listOf(
+                                    SubSubElement("1.1.1", "d1.1.1"),
+                                    SubSubElement("1.1.2", "d1.1.2")
+                                )
+                        ),
+                        SubElement(
+                            "1.2", "d1.2",
+                            subSubElements =
+                                listOf(
+                                    SubSubElement("1.2.1", "d1.2.1"),
+                                    SubSubElement("1.2.2", "d1.2.2"),
+                                )
+                        ),
+                    )
+                ),
+                Element(
+                    name = "element 2",
+                    subElements = listOf(
+                        SubElement(
+                            "2.1", "d2.1",
+                            subSubElements =
+                                listOf(
+                                    SubSubElement("2.1.1", "d2.1.1"),
+                                    SubSubElement("2.1.2", "d2.1.2"),
+                                )
+                        ),
+                        SubElement(
+                            "2.2", "d2.2",
+                            subSubElements =
+                                listOf(
+                                    SubSubElement("2.2.1", "d2.2.1"),
+                                    SubSubElement("2.2.2", "d2.2.2"),
+                                )
+                        ),
+                    )
+                ),
+            )
+        )
+
+        assertEncodedEquals(
+            value = data,
+            expectedToml = """
+                [[elements]]
+                    name = "element 1"
+                
+                    [[elements.subElements]]
+                        name = "1.1"
+                        description = "d1.1"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "1.1.1"
+                            description = "d1.1.1"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "1.1.2"
+                            description = "d1.1.2"
+                
+                    [[elements.subElements]]
+                        name = "1.2"
+                        description = "d1.2"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "1.2.1"
+                            description = "d1.2.1"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "1.2.2"
+                            description = "d1.2.2"
+                
+                [[elements]]
+                    name = "element 2"
+                
+                    [[elements.subElements]]
+                        name = "2.1"
+                        description = "d2.1"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "2.1.1"
+                            description = "d2.1.1"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "2.1.2"
+                            description = "d2.1.2"
+                
+                    [[elements.subElements]]
+                        name = "2.2"
+                        description = "d2.2"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "2.2.1"
+                            description = "d2.2.1"
+                
+                        [[elements.subElements.subSubElements]]
+                            name = "2.2.2"
+                            description = "d2.2.2"
+            """.trimIndent()
+        )
+    }
+
     @Test
     fun tableArrayWithNestedTableTest() {
         @Serializable