英文:
Kotlin - How to set Kotlinx.serialization Global Field Name Policy for different field cases
问题
我试图使每个JSON键都采用驼峰命名法,而不管它们的格式如何,比如:
ProjectName -> projectName
或
project_name -> projectName
使用Kotlin Serialization。
无法工作的方法
-
我想要在大规模上进行此操作,因此在每个参数上使用
@SerialName
不切实际,需要太多工作,而且容易出错。 -
遵循返回的JSON键命名策略会违反Kotlin代码样式指南,并且不向后兼容。
我尝试过的方法
示例代码
我查看了文档,并看到了这个示例。
然而,在JSON构建器上使用namingStrategy
不会按我想要的方式工作。
尽管下面的代码可以将类参数序列化为蛇形命名法:
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"}
}
下面的代码无法将帕斯卡命名法反序列化为蛇形命名法:
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")))
}
运行这段代码会导致以下错误:
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)
我的自定义实现
实施自定义命名策略会导致与上面相同的错误。
完整代码
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)
英文:
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
Things that do not work
-
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
Things I've tried
Example code
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)
My case implementation
Implementing custom name strategy resulted in the same error above
Full code
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")))
}
Error output
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)
答案1
得分: 0
显然我使用不当。没有一种策略可以同时解析 project_name
和 ProjectName
。为此,最好使用 @JsonNames
。
英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论