summaryrefslogtreecommitdiff
path: root/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
diff options
context:
space:
mode:
Diffstat (limited to 'formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt')
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt175
1 files changed, 39 insertions, 136 deletions
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
index e7533198..750b5449 100644
--- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
@@ -1,140 +1,43 @@
-/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
package kotlinx.serialization.hocon
-import com.typesafe.config.*
-import kotlinx.serialization.*
-import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.encoding.*
-import kotlinx.serialization.internal.*
-import kotlinx.serialization.modules.*
-
-@ExperimentalSerializationApi
-internal abstract class AbstractHoconEncoder(
- private val hocon: Hocon,
- private val valueConsumer: (ConfigValue) -> Unit,
-) : NamedValueEncoder() {
-
- override val serializersModule: SerializersModule
- get() = hocon.serializersModule
-
- private var writeDiscriminator: Boolean = false
-
- override fun elementName(descriptor: SerialDescriptor, index: Int): String {
- return descriptor.getConventionElementName(index, hocon.useConfigNamingConvention)
- }
-
- override fun composeName(parentName: String, childName: String): String = childName
-
- protected abstract fun encodeTaggedConfigValue(tag: String, value: ConfigValue)
- protected abstract fun getCurrent(): ConfigValue
-
- override fun encodeTaggedValue(tag: String, value: Any) = encodeTaggedConfigValue(tag, configValueOf(value))
- override fun encodeTaggedNull(tag: String) = encodeTaggedConfigValue(tag, configValueOf(null))
- override fun encodeTaggedChar(tag: String, value: Char) = encodeTaggedString(tag, value.toString())
-
- override fun encodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor, ordinal: Int) {
- encodeTaggedString(tag, enumDescriptor.getElementName(ordinal))
- }
-
- override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = hocon.encodeDefaults
-
- override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
- if (serializer !is AbstractPolymorphicSerializer<*> || hocon.useArrayPolymorphism) {
- serializer.serialize(this, value)
- return
- }
-
- @Suppress("UNCHECKED_CAST")
- val casted = serializer as AbstractPolymorphicSerializer<Any>
- val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
- writeDiscriminator = true
-
- actualSerializer.serialize(this, value)
- }
-
- override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
- val consumer =
- if (currentTagOrNull == null) valueConsumer
- else { value -> encodeTaggedConfigValue(currentTag, value) }
- val kind = descriptor.hoconKind(hocon.useArrayPolymorphism)
-
- return when {
- kind.listLike -> HoconConfigListEncoder(hocon, consumer)
- kind.objLike -> HoconConfigEncoder(hocon, consumer)
- kind == StructureKind.MAP -> HoconConfigMapEncoder(hocon, consumer)
- else -> this
- }.also { encoder ->
- if (writeDiscriminator) {
- encoder.encodeTaggedString(hocon.classDiscriminator, descriptor.serialName)
- writeDiscriminator = false
- }
- }
- }
-
- override fun endEncode(descriptor: SerialDescriptor) {
- valueConsumer(getCurrent())
- }
-
- private fun configValueOf(value: Any?) = ConfigValueFactory.fromAnyRef(value)
-}
-
-@ExperimentalSerializationApi
-internal class HoconConfigEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
- AbstractHoconEncoder(hocon, configConsumer) {
-
- private val configMap = mutableMapOf<String, ConfigValue>()
-
- override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
- configMap[tag] = value
- }
-
- override fun getCurrent(): ConfigValue = ConfigValueFactory.fromMap(configMap)
-}
-
-@ExperimentalSerializationApi
-internal class HoconConfigListEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
- AbstractHoconEncoder(hocon, configConsumer) {
-
- private val values = mutableListOf<ConfigValue>()
-
- override fun elementName(descriptor: SerialDescriptor, index: Int): String = index.toString()
-
- override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
- values.add(tag.toInt(), value)
- }
-
- override fun getCurrent(): ConfigValue = ConfigValueFactory.fromIterable(values)
-}
-
+import com.typesafe.config.ConfigValue
+import kotlinx.serialization.ExperimentalSerializationApi
+
+/**
+ * Encoder used by Hocon during serialization.
+ * This interface allows intercepting serialization process and insertion of arbitrary [ConfigValue] into the output.
+ *
+ * Usage example (nested config serialization):
+ * ```
+ * @Serializable
+ * data class Example(
+ * @Serializable(NestedConfigSerializer::class)
+ * val d: Config
+ * )
+ * object NestedConfigSerializer : KSerializer<Config> {
+ * override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("package.Config", PrimitiveKind.STRING)
+ *
+ * override fun deserialize(decoder: Decoder): Config =
+ * if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.getConfig(path) }
+ * else throw SerializationException("This class can be decoded only by Hocon format")
+ *
+ * override fun serialize(encoder: Encoder, value: Config) {
+ * if (encoder is HoconEncoder) encoder.encodeConfigValue(value.root())
+ * else throw SerializationException("This class can be encoded only by Hocon format")
+ * }
+ * }
+ * val nestedConfig = ConfigFactory.parseString("nested { value = \"test\" }")
+ * val globalConfig = Hocon.encodeToConfig(Example(nestedConfig)) // d: { nested: { value = "test" } }
+ * val newNestedConfig = Hocon.decodeFromConfig(Example.serializer(), globalConfig)
+ * ```
+ */
@ExperimentalSerializationApi
-internal class HoconConfigMapEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
- AbstractHoconEncoder(hocon, configConsumer) {
-
- private val configMap = mutableMapOf<String, ConfigValue>()
-
- private lateinit var key: String
- private var isKey: Boolean = true
-
- override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
- if (isKey) {
- key = when (value.valueType()) {
- ConfigValueType.OBJECT, ConfigValueType.LIST -> throw InvalidKeyKindException(value)
- else -> value.unwrappedNullable().toString()
- }
- isKey = false
- } else {
- configMap[key] = value
- isKey = true
- }
- }
-
- override fun getCurrent(): ConfigValue = ConfigValueFactory.fromMap(configMap)
-
- // Without cast to `Any?` Kotlin will assume unwrapped value as non-nullable by default
- // and will call `Any.toString()` instead of extension-function `Any?.toString()`.
- // We can't cast value in place using `(value.unwrapped() as Any?).toString()` because of warning "No cast needed".
- private fun ConfigValue.unwrappedNullable(): Any? = unwrapped()
+sealed interface HoconEncoder {
+
+ /**
+ * Appends the given [ConfigValue] element to the current output.
+ *
+ * @param value to insert
+ */
+ fun encodeConfigValue(value: ConfigValue)
}