diff options
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.kt | 175 |
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) } |