diff --git a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XMLEncoder.kt b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XMLEncoder.kt index 9f2dc1b5d..3ff2c5f1c 100644 --- a/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XMLEncoder.kt +++ b/serialization/src/commonMain/kotlin/nl/adaptivity/xmlutil/serialization/XMLEncoder.kt @@ -1,7 +1,7 @@ /* - * Copyright (c) 2018. + * Copyright (c) 2024. * - * This file is part of XmlUtil. + * This file is part of xmlutil. * * This file is licenced to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance @@ -409,7 +409,9 @@ internal open class XmlEncoderBase internal constructor( ) : XmlEncoder(parent.xmlDescriptor.getElementDescriptor(childIndex), childIndex, null) { override fun encodeString(value: String) { val d = xmlDescriptor.getElementDescriptor(0) - parent.encodeStringElement(d, childIndex, value) + parent.defer(childIndex, forceDefer = true) { + parent.encodeStringElement(d, childIndex, value) + } } override fun encodeSerializableValue( @@ -417,12 +419,14 @@ internal open class XmlEncoderBase internal constructor( value: T ) { val d = xmlDescriptor.getElementDescriptor(0) - parent.encodeSerializableElement( - d, - childIndex, - serializer, - value - ) + parent.defer(childIndex, forceDefer = true) { + parent.encodeSerializableElement( + d, + childIndex, + serializer, + value + ) + } } @ExperimentalSerializationApi @@ -528,13 +532,16 @@ internal open class XmlEncoderBase internal constructor( } @OptIn(ExperimentalSerializationApi::class) - open fun defer(index: Int, deferred: CompositeEncoder.() -> Unit) { - if (xmlDescriptor.getElementDescriptor(index).doInline) { - deferred() // Don't defer inline values as it has a problem with the value serializer deferring + open fun defer(index: Int, forceDefer: Boolean = false, deferred: CompositeEncoder.() -> Unit) { + if (! forceDefer && xmlDescriptor.getElementDescriptor(index).doInline) { + // Don't defer inline values as it has a problem with the value serializer deferring + deferred() } else if (!deferring) { // We should never defer if we are processing deferred elements deferred() } else if (reorderInfo != null) { deferredBuffer.add(reorderInfo[index] to deferred) + } else if (forceDefer) { + deferredBuffer.add(index to deferred) } else { val outputKind = xmlDescriptor.getElementDescriptor(index).outputKind @@ -902,8 +909,10 @@ internal open class XmlEncoderBase internal constructor( override fun defer( index: Int, + forceDefer: Boolean, deferred: CompositeEncoder.() -> Unit ) { + // Polymorphic types are "hardcoded" so don't need deferring deferred() } @@ -1000,7 +1009,8 @@ internal open class XmlEncoderBase internal constructor( TagEncoder(xmlDescriptor, null) { private lateinit var entryKey: QName - override fun defer(index: Int, deferred: CompositeEncoder.() -> Unit) { + override fun defer(index: Int, forceDefer: Boolean, deferred: CompositeEncoder.() -> Unit) { + // a map, where deferring is never needed deferred() } @@ -1078,7 +1088,10 @@ internal open class XmlEncoderBase internal constructor( } } - override fun defer(index: Int, deferred: CompositeEncoder.() -> Unit) = deferred() + override fun defer(index: Int, forceDefer: Boolean, deferred: CompositeEncoder.() -> Unit) { + // This is a list, deferring is not relevant. + deferred() + } override fun writeBegin() {} @@ -1133,8 +1146,10 @@ internal open class XmlEncoderBase internal constructor( override fun defer( index: Int, + forceDefer: Boolean, deferred: CompositeEncoder.() -> Unit ) { + // Deferring in a list is not needed (this is for list elements, not the list itself. deferred() } @@ -1193,7 +1208,8 @@ internal open class XmlEncoderBase internal constructor( private lateinit var keySerializer: SerializationStrategy<*> private var keyValue: Any? = null - override fun defer(index: Int, deferred: CompositeEncoder.() -> Unit) { + override fun defer(index: Int, forceDefer: Boolean, deferred: CompositeEncoder.() -> Unit) { + // deferring is never valid in a map, ther should not be any reordering either deferred() } diff --git a/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/regressions/ValueMemberSerialOrder195.kt b/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/regressions/ValueMemberSerialOrder195.kt new file mode 100644 index 000000000..dddf21e5f --- /dev/null +++ b/serialization/src/commonTest/kotlin/nl/adaptivity/xml/serialization/regressions/ValueMemberSerialOrder195.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024. + * + * This file is part of xmlutil. + * + * This file is licenced to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You should have received a copy of the license with the source distribution. + * Alternatively, you may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package nl.adaptivity.xml.serialization.regressions + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XML +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import kotlin.jvm.JvmInline +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Value members should not be ordered differently. + */ +class ValueMemberSerialOrder195 { + + @Test + fun testOrder() { + val foo = Foo("a", Foo.Code("b")) + assertEquals( + expected = "ab", + actual = XML.encodeToString(foo) // ba + ) + } + + @Serializable + @XmlSerialName("Foo") + data class Foo( + @XmlElement(true) + @XmlSerialName("ServiceName","", "") + val serviceName: String? = null, + + @XmlElement + @XmlSerialName("SendingSystem","", "") + val sendingSystem: Code? = null, + ) { + @JvmInline + @Serializable + @XmlSerialName("Code","", "") + value class Code( + val `value`: String, + ) + } +}