I'm trying to make every json key camel-case regardless of how it's formatted. Like:
ProjectName -> projectName
or
project_name -> projectName
with kotlin serialization
I want to do this on a large scale so using @SerialName
on every single parameter is not plausible, requires too much work and is error prone
Following returned json key naming strategy breaks kotlin code style guides and would be backward incompatible
I looked at the docs and saw this example
However using namingStrategy
on Json builder, won't work the way I wanted to
Although below code works to serialize class parameters to snake case
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@OptIn(ExperimentalSerializationApi::class)
fun main() {
@Serializable
data class Project(val projectName: String, val projectOwner: String)
val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
// {"project_name":"kotlinx.serialization","project_owner":"Kotlin"}
}
Below code doesn't work to deserialize pascal case to snake case
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@OptIn(ExperimentalSerializationApi::class)
fun main() {
@Serializable
data class Project(val project_name: String, val project_owner: String)
val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
val project = format.decodeFromString<Project>("""{"ProjectName":"kotlinx.coroutines", "ProjectOwner":"Kotlin"}""")
println(format.encodeToString(project.copy(project_name = "kotlinx.serialization")))
}
Running this code results in:
Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 2: Encountered an unknown key 'ProjectName' at path: $
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
JSON input: {"ProjectName":"kotlinx.coroutines", "ProjectOwner":"Kotlin"}
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
at kotlinx.serialization.json.internal.AbstractJsonLexer.fail(AbstractJsonLexer.kt:584)
at kotlinx.serialization.json.internal.AbstractJsonLexer.failOnUnknownKey(AbstractJsonLexer.kt:579)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.handleUnknown(StreamingJsonDecoder.kt:254)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeObjectIndex(StreamingJsonDecoder.kt:240)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeElementIndex(StreamingJsonDecoder.kt:175)
at MainKt$main$Project$$serializer.deserialize(Main.kt:9)
at MainKt$main$Project$$serializer.deserialize(Main.kt:9)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:70)
at kotlinx.serialization.json.Json.decodeFromString(Json.kt:107)
at MainKt.main(Main.kt:17)
at MainKt.main(Main.kt)
Implementing custom name strategy resulted in the same error above
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
@OptIn(ExperimentalSerializationApi::class)
public object CamelCase : JsonNamingStrategy {
override fun serialNameForJson(descriptor: SerialDescriptor, elementIndex: Int, serialName: String): String {
val words = serialName.split(Regex("[^a-zA-Z0-9]+"))
return buildString {
words.forEachIndexed { index, word ->
if (index == 0) {
append(word.lowercase())
} else {
append(word.replaceFirstChar(Char::titlecase))
}
}
}
}
override fun toString(): String = "CamelCase"
}
@OptIn(ExperimentalSerializationApi::class)
public fun main() {
@Serializable
data class Project(val projectName: String, val projectOwner: String)
val format = Json { namingStrategy = CamelCase }
val project =
format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
}
Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 2: Encountered an unknown key 'project_name' at path: $
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
JSON input: {"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
at kotlinx.serialization.json.internal.AbstractJsonLexer.fail(AbstractJsonLexer.kt:584)
at kotlinx.serialization.json.internal.AbstractJsonLexer.failOnUnknownKey(AbstractJsonLexer.kt:579)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.handleUnknown(StreamingJsonDecoder.kt:254)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeObjectIndex(StreamingJsonDecoder.kt:240)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeElementIndex(StreamingJsonDecoder.kt:175)
at MainKt$main$Project$$serializer.deserialize(Main.kt:28)
at MainKt$main$Project$$serializer.deserialize(Main.kt:28)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:70)
at kotlinx.serialization.json.Json.decodeFromString(Json.kt:107)
at MainKt.main(Main.kt:37)
at MainKt.main(Main.kt)
Apparently I was using it wrong. There's no strategy that can parse both project_name
and ProjectName
.
For that, it's better to use @JsonNames