@@ -25,24 +25,45 @@ import kotlinx.serialization.modules.*
25
25
*/
26
26
@ExperimentalSerializationApi
27
27
public sealed class Hocon (
28
- internal val useConfigNamingConvention : Boolean ,
29
- internal val useArrayPolymorphism : Boolean ,
30
- internal val classDiscriminator : String ,
31
- override val serializersModule : SerializersModule
28
+ internal val encodeDefaults : Boolean ,
29
+ internal val useConfigNamingConvention : Boolean ,
30
+ internal val useArrayPolymorphism : Boolean ,
31
+ internal val classDiscriminator : String ,
32
+ override val serializersModule : SerializersModule ,
32
33
) : SerialFormat {
33
34
35
+ /* *
36
+ * Decodes the given [config] into a value of type [T] using the given serializer.
37
+ */
34
38
@ExperimentalSerializationApi
35
39
public fun <T > decodeFromConfig (deserializer : DeserializationStrategy <T >, config : Config ): T =
36
40
ConfigReader (config).decodeSerializableValue(deserializer)
37
41
38
42
/* *
39
- * The default instance of Hocon parser.
43
+ * Encodes the given [value] into a [Config] using the given [serializer].
44
+ * @throws SerializationException If list or primitive type passed as a [value].
40
45
*/
41
46
@ExperimentalSerializationApi
42
- public companion object Default : Hocon(false , false , " type" , EmptySerializersModule ) {
43
- private val NAMING_CONVENTION_REGEX by lazy { " [A-Z]" .toRegex() }
47
+ public fun <T > encodeToConfig (serializer : SerializationStrategy <T >, value : T ): Config {
48
+ lateinit var configValue: ConfigValue
49
+ val encoder = HoconConfigEncoder (this ) { configValue = it }
50
+ encoder.encodeSerializableValue(serializer, value)
51
+
52
+ if (configValue !is ConfigObject ) {
53
+ throw SerializationException (
54
+ " Value of type '${configValue.valueType()} ' can't be used at the root of HOCON Config. " +
55
+ " It should be either object or map."
56
+ )
57
+ }
58
+ return (configValue as ConfigObject ).toConfig()
44
59
}
45
60
61
+ /* *
62
+ * The default instance of Hocon parser.
63
+ */
64
+ @ExperimentalSerializationApi
65
+ public companion object Default : Hocon(false , false , false , " type" , EmptySerializersModule )
66
+
46
67
private abstract inner class ConfigConverter <T > : TaggedDecoder <T >() {
47
68
override val serializersModule: SerializersModule
48
69
get() = this @Hocon.serializersModule
@@ -59,8 +80,7 @@ public sealed class Hocon(
59
80
}
60
81
} catch (e: ConfigException ) {
61
82
val configOrigin = e.origin()
62
- val requiredType = E ::class .simpleName
63
- throw SerializationException (" ${configOrigin.description()} required to be of type $requiredType " )
83
+ throw ConfigValueTypeCastException <E >(configOrigin)
64
84
}
65
85
}
66
86
@@ -109,13 +129,7 @@ public sealed class Hocon(
109
129
if (parentName.isEmpty()) childName else " $parentName .$childName "
110
130
111
131
override fun SerialDescriptor.getTag (index : Int ): String =
112
- composeName(currentTagOrNull ? : " " , getConventionElementName(index))
113
-
114
- private fun SerialDescriptor.getConventionElementName (index : Int ): String {
115
- val originalName = getElementName(index)
116
- return if (! useConfigNamingConvention) originalName
117
- else originalName.replace(NAMING_CONVENTION_REGEX ) { " -${it.value.lowercase()} " }
118
- }
132
+ composeName(currentTagOrNull.orEmpty(), getConventionElementName(index, useConfigNamingConvention))
119
133
120
134
override fun decodeNotNullMark (): Boolean {
121
135
// Tag might be null for top-level deserialization
@@ -133,24 +147,14 @@ public sealed class Hocon(
133
147
val reader = ConfigReader (config)
134
148
val type = reader.decodeTaggedString(classDiscriminator)
135
149
val actualSerializer = deserializer.findPolymorphicSerializerOrNull(reader, type)
136
- ? : throwSerializerNotFound (type)
150
+ ? : throw SerializerNotFoundException (type)
137
151
138
152
@Suppress(" UNCHECKED_CAST" )
139
153
return (actualSerializer as DeserializationStrategy <T >).deserialize(reader)
140
154
}
141
155
142
- private fun throwSerializerNotFound (type : String? ): Nothing {
143
- val suffix = if (type == null ) " missing class discriminator ('null')" else " class discriminator '$type '"
144
- throw SerializationException (" Polymorphic serializer was not found for $suffix " )
145
- }
146
-
147
156
override fun beginStructure (descriptor : SerialDescriptor ): CompositeDecoder {
148
- val kind = when (descriptor.kind) {
149
- is PolymorphicKind -> {
150
- if (useArrayPolymorphism) StructureKind .LIST else StructureKind .MAP
151
- }
152
- else -> descriptor.kind
153
- }
157
+ val kind = descriptor.hoconKind(useArrayPolymorphism)
154
158
155
159
return when {
156
160
kind.listLike -> ListConfigReader (conf.getList(currentTag))
@@ -239,28 +243,31 @@ public sealed class Hocon(
239
243
throw SerializationException (" $serialName does not contain element with name '$name '" )
240
244
return index
241
245
}
242
-
243
- private val SerialKind .listLike get() = this == StructureKind .LIST || this is PolymorphicKind
244
- private val SerialKind .objLike get() = this == StructureKind .CLASS || this == StructureKind .OBJECT
245
246
}
246
247
247
248
/* *
248
- * Decodes the given [config] into a value of type [T] using a deserialize retrieved
249
- * from reified type parameter.
249
+ * Decodes the given [config] into a value of type [T] using a deserializer retrieved
250
+ * from the reified type parameter.
250
251
*/
251
252
@ExperimentalSerializationApi
252
253
public inline fun <reified T > Hocon.decodeFromConfig (config : Config ): T =
253
254
decodeFromConfig(serializersModule.serializer(), config)
254
255
256
+ /* *
257
+ * Encodes the given [value] of type [T] into a [Config] using a serializer retrieved
258
+ * from the reified type parameter.
259
+ */
260
+ @ExperimentalSerializationApi
261
+ public inline fun <reified T > Hocon.encodeToConfig (value : T ): Config =
262
+ encodeToConfig(serializersModule.serializer(), value)
263
+
255
264
/* *
256
265
* Creates an instance of [Hocon] configured from the optionally given [Hocon instance][from]
257
266
* and adjusted with [builderAction].
258
267
*/
259
268
@ExperimentalSerializationApi
260
269
public fun Hocon (from : Hocon = Hocon , builderAction : HoconBuilder .() -> Unit ): Hocon {
261
- val builder = HoconBuilder (from)
262
- builder.builderAction()
263
- return HoconImpl (builder.useConfigNamingConvention, builder.useArrayPolymorphism, builder.classDiscriminator, builder.serializersModule)
270
+ return HoconImpl (HoconBuilder (from).apply (builderAction))
264
271
}
265
272
266
273
/* *
@@ -273,6 +280,12 @@ public class HoconBuilder internal constructor(hocon: Hocon) {
273
280
*/
274
281
public var serializersModule: SerializersModule = hocon.serializersModule
275
282
283
+ /* *
284
+ * Specifies whether default values of Kotlin properties should be encoded.
285
+ * `false` by default.
286
+ */
287
+ public var encodeDefaults: Boolean = hocon.encodeDefaults
288
+
276
289
/* *
277
290
* Switches naming resolution to config naming convention: hyphen separated.
278
291
*/
@@ -293,9 +306,10 @@ public class HoconBuilder internal constructor(hocon: Hocon) {
293
306
}
294
307
295
308
@OptIn(ExperimentalSerializationApi ::class )
296
- private class HoconImpl (
297
- useConfigNamingConvention : Boolean ,
298
- useArrayPolymorphism : Boolean ,
299
- classDiscriminator : String ,
300
- serializersModule : SerializersModule
301
- ) : Hocon(useConfigNamingConvention, useArrayPolymorphism, classDiscriminator, serializersModule)
309
+ private class HoconImpl (hoconBuilder : HoconBuilder ) : Hocon(
310
+ encodeDefaults = hoconBuilder.encodeDefaults,
311
+ useConfigNamingConvention = hoconBuilder.useConfigNamingConvention,
312
+ useArrayPolymorphism = hoconBuilder.useArrayPolymorphism,
313
+ classDiscriminator = hoconBuilder.classDiscriminator,
314
+ serializersModule = hoconBuilder.serializersModule
315
+ )
0 commit comments