Kotlin – 如何为不同的字段命名情况设置Kotlinx.serialization全局字段名称策略

huangapple go评论61阅读模式
英文:

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 -&gt; projectName

or

  • project_name -&gt; 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&lt;Project&gt;(&quot;&quot;&quot;{&quot;project_name&quot;:&quot;kotlinx.coroutines&quot;, &quot;project_owner&quot;:&quot;Kotlin&quot;}&quot;&quot;&quot;)
    println(format.encodeToString(project.copy(projectName = &quot;kotlinx.serialization&quot;)))
// {&quot;project_name&quot;:&quot;kotlinx.serialization&quot;,&quot;project_owner&quot;:&quot;Kotlin&quot;}
}

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&lt;Project&gt;(&quot;&quot;&quot;{&quot;ProjectName&quot;:&quot;kotlinx.coroutines&quot;, &quot;ProjectOwner&quot;:&quot;Kotlin&quot;}&quot;&quot;&quot;)
    println(format.encodeToString(project.copy(project_name = &quot;kotlinx.serialization&quot;)))
}

Running this code results in:

Exception in thread &quot;main&quot; kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 2: Encountered an unknown key &#39;ProjectName&#39; at path: $
Use &#39;ignoreUnknownKeys = true&#39; in &#39;Json {}&#39; builder to ignore unknown keys.
JSON input: {&quot;ProjectName&quot;:&quot;kotlinx.coroutines&quot;, &quot;ProjectOwner&quot;:&quot;Kotlin&quot;}
	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(&quot;[^a-zA-Z0-9]+&quot;))
        return buildString {
            words.forEachIndexed { index, word -&gt;
                if (index == 0) {
                    append(word.lowercase())
                } else {
                    append(word.replaceFirstChar(Char::titlecase))
                }
            }
        }
    }

    override fun toString(): String = &quot;CamelCase&quot;
}

@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&lt;Project&gt;(&quot;&quot;&quot;{&quot;project_name&quot;:&quot;kotlinx.coroutines&quot;, &quot;project_owner&quot;:&quot;Kotlin&quot;}&quot;&quot;&quot;)
    println(format.encodeToString(project.copy(projectName = &quot;kotlinx.serialization&quot;)))
}
Error output
Exception in thread &quot;main&quot; kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 2: Encountered an unknown key &#39;project_name&#39; at path: $
Use &#39;ignoreUnknownKeys = true&#39; in &#39;Json {}&#39; builder to ignore unknown keys.
JSON input: {&quot;project_name&quot;:&quot;kotlinx.coroutines&quot;, &quot;project_owner&quot;:&quot;Kotlin&quot;}
	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_nameProjectName。为此,最好使用 @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

huangapple
  • 本文由 发表于 2023年6月15日 20:51:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76482678.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定