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
Show file tree
Hide file tree
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
Expand Up @@ -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;
Expand Down
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
Expand Up @@ -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()}" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
"""
)
}
Expand Down