Skip to content

Add @HoconName annotation #3013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions formats/hocon/api/kotlinx-serialization-hocon.api
Original file line number Diff line number Diff line change
@@ -35,6 +35,15 @@ public final class kotlinx/serialization/hocon/HoconKt {
public static synthetic fun Hocon$default (Lkotlinx/serialization/hocon/Hocon;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/hocon/Hocon;
}

public abstract interface annotation class kotlinx/serialization/hocon/HoconName : java/lang/annotation/Annotation {
public abstract fun value ()Ljava/lang/String;
}

public synthetic class kotlinx/serialization/hocon/HoconName$Impl : kotlinx/serialization/hocon/HoconName {
public fun <init> (Ljava/lang/String;)V
public final synthetic fun value ()Ljava/lang/String;
}

public final class kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/typesafe/config/ConfigMemorySize;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.hocon

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialInfo
import kotlinx.serialization.SerialName

/**
* This annotation has a higher priority than [SerialName] or [Hocon.useConfigNamingConvention].
* This means that you have full control over property name for encoding and decoding in the HOCON format in practice.
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
@ExperimentalSerializationApi
public annotation class HoconName(val value: String)
Original file line number Diff line number Diff line change
@@ -7,6 +7,10 @@ private val NAMING_CONVENTION_REGEX by lazy { "[A-Z]".toRegex() }

@OptIn(ExperimentalSerializationApi::class)
internal fun SerialDescriptor.getConventionElementName(index: Int, useConfigNamingConvention: Boolean): String {
val hoconName = getElementAnnotations(index).firstOrNull { it is HoconName } as HoconName?
if (hoconName != null) {
return hoconName.value
}
val originalName = getElementName(index)
return if (!useConfigNamingConvention) originalName
else originalName.replace(NAMING_CONVENTION_REGEX) { "-${it.value.lowercase()}" }
Original file line number Diff line number Diff line change
@@ -14,10 +14,10 @@ class HoconNamingConventionTest {
data class CaseConfig(val aCharValue: Char, val aStringValue: String)

@Serializable
data class SerialNameConfig(@SerialName("an-id-value") val anIDValue: Int)
data class HoconNameConfig(@HoconName("anID-value") val anIDValue: Int)

@Serializable
data class CaseWithInnerConfig(val caseConfig: CaseConfig, val serialNameConfig: SerialNameConfig)
data class CaseWithInnerConfig(val caseConfig: CaseConfig, val hoconNameConfig: HoconNameConfig)

private val hocon = Hocon {
useConfigNamingConvention = true
@@ -39,42 +39,42 @@ class HoconNamingConventionTest {
}

@Test
fun testDeserializeUsingSerialNameInsteadOfNamingConvention() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was successful only because the value in @SerialName is follow the naming convention 😄
I think it is confusing and not helpful to check anything.

val obj = deserializeConfig("an-id-value = 42", SerialNameConfig.serializer(), true)
fun testDeserializeUsingHoconNameInsteadOfNamingConvention() {
val obj = deserializeConfig("anID-value = 42", HoconNameConfig.serializer(), true)
assertEquals(42, obj.anIDValue)
}

@Test
fun testSerializeUsingSerialNameInsteadOfNamingConvention() {
val obj = SerialNameConfig(anIDValue = 42)
fun testSerializeUsingHoconNameInsteadOfNamingConvention() {
val obj = HoconNameConfig(anIDValue = 42)
val config = hocon.encodeToConfig(obj)

config.assertContains("an-id-value = 42")
config.assertContains("anID-value = 42")
}

@Test
fun testDeserializeInnerValuesUsingNamingConvention() {
val configString = "case-config {a-char-value = b, a-string-value = bar}, serial-name-config {an-id-value = 21}"
val configString = "case-config {a-char-value = b, a-string-value = bar}, hocon-name-config {anID-value = 21}"
val obj = deserializeConfig(configString, CaseWithInnerConfig.serializer(), true)
with(obj.caseConfig) {
assertEquals('b', aCharValue)
assertEquals("bar", aStringValue)
}
assertEquals(21, obj.serialNameConfig.anIDValue)
assertEquals(21, obj.hoconNameConfig.anIDValue)
}

@Test
fun testSerializeInnerValuesUsingNamingConvention() {
val obj = CaseWithInnerConfig(
caseConfig = CaseConfig(aCharValue = 't', aStringValue = "test"),
serialNameConfig = SerialNameConfig(anIDValue = 42)
hoconNameConfig = HoconNameConfig(anIDValue = 42)
)
val config = hocon.encodeToConfig(obj)

config.assertContains(
"""
case-config { a-char-value = t, a-string-value = test }
serial-name-config { an-id-value = 42 }
hocon-name-config { anID-value = 42 }
"""
)
}