Kotlin / kotlinx.serialization

Kotlin multiplatform / multi-format serialization
Apache License 2.0
5.37k stars 620 forks source link

@Flat option annotation #292

Open InsanusMokrassar opened 5 years ago

InsanusMokrassar commented 5 years ago

I offer to include @Flat (or @Flatten, for example) annotation which will be used in cases when value object of field must be included directly into parent object.

@Serializable
data class Parent(
    val stringField: String = "stringValue",
    @Flat
    val flatField: Child = Child()
)

@Serializable
class Child {
    val childString: String = "childValue"
}

fun main(args: Array<String>) {
    println(JSON.stringify(Parent.serializer(), Parent()))
}

Will give result:

{"stringField":"stringValue","childString":"childValue"}

Here I see a few problems:

InsanusMokrassar commented 5 years ago

So, next code will give the same effect:

@Serializable(ParentFlatChildSerializer::class)
data class Parent(
    val stringField: String = "stringValue",
    val flatField: Child = Child()
)

@Serializable
class Child {
    @Optional
    val flatFieldValue: String = "flatFieldValue"
}

@Serializer(Parent::class)
object ParentFlatChildSerializer : KSerializer<Parent> {
    override fun serialize(output: Encoder, obj: Parent) {
        output.beginStructure(
            descriptor
        ).apply {
            encodeStringElement(
                descriptor, 0, obj.stringField
            )
            encodeStringElement(Child.serializer().descriptor, 0, obj.flatField.flatFieldValue)
        }.endStructure(
            descriptor
        )
    }
}

fun main(args: Array<String>) {
    println(JSON.stringify(ParentFlatChildSerializer, Parent()))
}

But it is much longer and is not useful to write serializer like this for each object/class in projects.

Coneys commented 3 years ago

Any information about this? Is there some workaround, or will it be implemented in near future?

Elvis10ten commented 2 years ago

This would be nice to have.

marcosalis commented 2 years ago

I join the queue to request this feature. It would be extremely useful indeed. Thank you!

micHar commented 2 years ago

Same here

pdvrieze commented 2 years ago

There are two workarounds. One is inline classes (or tricking the serialization framework into thinking the class is inline - using a custom SerialDescriptor). The other is to use a custom serializer. If you give its descriptor as PrimitiveKind.String (there is a factory function for primitive descriptors, not even "internal"), then you can have the implementation just use encodeString, decodeString and do the "right" translation to get the objects.

What isn't really possible is to embed multi-attribute members into a parent (neither way really works), and that would require quite some changes on the compiler plugin side.

CharlieTap commented 1 year ago

Rusts serde has a great impl of this, could be of use when designing the API if its pursued

monosoul commented 1 year ago

Not the best solution, but a bit better than writing a full serializer. Uses JsonTransformingSerializer:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonTransformingSerializer
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonObject

@Serializable
data class Parent(
    val stringField: String = "stringValue",
    val flatField: Child = Child()
)

@Serializable
class Child {
    val childString: String = "childValue"
    val otherChildString: String = "otherChildVale"
}

object UnwrappingJsonSerializer: JsonTransformingSerializer<Parent>(Parent.serializer()) {
    override fun transformSerialize(element: JsonElement) = buildJsonObject {
        element.jsonObject.forEach { (propertyName, propertyValue) ->
            if (propertyName == Parent::flatField.name) {
                propertyValue.jsonObject.forEach(::put)
            } else {
                put(propertyName, propertyValue)
            }
        }
    }
}

fun main(args: Array<String>) {
    println(
        Json {
            prettyPrint = true
            encodeDefaults = true
        }.encodeToString(
            serializer = UnwrappingJsonSerializer,
            value = Parent()
        )
    )
}

will print:

{
    "stringField": "stringValue",
    "childString": "childValue",
    "otherChildString": "otherChildVale"
}
consp1racy commented 5 months ago

AndroidX Room calls this @Embedded. There's another issue with the same goal here https://github.com/Kotlin/kotlinx.serialization/issues/1734