无法在多平台项目中使用 kotlinx.serialization
Unable to use kotlinx.serialization in multiplatform project
我正在尝试在多平台 (JVM/JS) 项目中使用 kotlinx.serialization。
当我在公共模块中的某些 class 中向某些数据 class 添加 @Serializable
注释时:
@Serializable
data class User(
val user: String
)
构建成功,没有错误,但看起来 encoders/decoders 没有生成。
在 build/generated-src
中,我没有看到任何提供 encodeToString
扩展功能的相关 kotlin 文件,当我尝试使用类似的东西时:
JSON.encodeToString(User(login = "X"))
我收到未解决的引用错误:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public inline fun <reified T> StringFormat.encodeToString(value: TypeVariable(T)): String defined in kotlinx.serialization
如能帮助解决此问题,我们将不胜感激。提前致谢。
我的build.gradle.kts:
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
/* buildscript {
dependencies {
val kotlinVersion: String by System.getProperties()
classpath(kotlin("serialization", version = kotlinVersion))
}
} */
plugins {
val kotlinVersion: String by System.getProperties()
kotlin("multiplatform") version kotlinVersion
id("kotlinx-serialization") version kotlinVersion
val kvisionVersion: String by System.getProperties()
id("kvision") version kvisionVersion
}
version = "1.0.0-SNAPSHOT"
group = "tech.lorefnon"
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
maven { url = uri("https://repo.spring.io/milestone") }
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
mavenLocal()
}
// Versions
val kotlinVersion: String by System.getProperties()
val kvisionVersion: String by System.getProperties()
val ktorVersion: String by project
val logbackVersion: String by project
val commonsCodecVersion: String by project
val webDir = file("src/frontendMain/web")
val mainClassName = "io.ktor.server.netty.EngineMain"
kotlin {
jvm("backend") {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = listOf("-Xjsr305=strict")
}
}
}
js("frontend") {
browser {
runTask {
outputFileName = "main.bundle.js"
sourceMaps = false
devServer = KotlinWebpackConfig.DevServer(
open = false,
port = 3000,
proxy = mapOf(
"/kv/*" to "http://localhost:8080",
"/login" to "http://localhost:8080",
"/logout" to "http://localhost:8080",
"/kvws/*" to mapOf("target" to "ws://localhost:8080", "ws" to true)
),
contentBase = listOf("$buildDir/processedResources/frontend/main")
)
}
webpackTask {
outputFileName = "main.bundle.js"
}
testTask {
useKarma {
useChromeHeadless()
}
}
}
binaries.executable()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
api("pl.treksoft:kvision-server-ktor:$kvisionVersion")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
}
kotlin.srcDir("build/generated-src/common")
kotlin.srcDir("src/commonMain/kotlin")
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val backendMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
implementation("com.auth0:java-jwt:3.11.0")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-auth:$ktorVersion")
implementation("io.ktor:ktor-auth-jwt:$ktorVersion")
implementation("ch.qos.logback:logback-classic:$logbackVersion")
implementation("commons-codec:commons-codec:$commonsCodecVersion")
implementation("org.redisson:redisson:3.14.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.2")
}
}
val backendTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val frontendMain by getting {
resources.srcDir(webDir)
dependencies {
implementation("pl.treksoft:kvision:$kvisionVersion")
implementation("pl.treksoft:kvision-redux:${kvisionVersion}")
implementation("pl.treksoft:kvision-bootstrap:$kvisionVersion")
implementation("pl.treksoft:kvision-bootstrap-select:$kvisionVersion")
implementation("pl.treksoft:kvision-datacontainer:$kvisionVersion")
implementation("pl.treksoft:kvision-bootstrap-dialog:$kvisionVersion")
implementation("pl.treksoft:kvision-fontawesome:$kvisionVersion")
implementation("pl.treksoft:kvision-i18n:$kvisionVersion")
implementation(npm("redux-logger", "3.0.6"))
}
kotlin.srcDir("build/generated-src/frontend")
}
val frontendTest by getting {
dependencies {
implementation(kotlin("test-js"))
implementation("pl.treksoft:kvision-testutils:$kvisionVersion:tests")
}
}
}
}
fun getNodeJsBinaryExecutable(): String {
val nodeDir = NodeJsRootPlugin.apply(project).nodeJsSetupTaskProvider.get().destination
val isWindows = System.getProperty("os.name").toLowerCase().contains("windows")
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
val command = NodeJsRootPlugin.apply(project).nodeCommand
val finalCommand = if (isWindows && command == "node") "node.exe" else command
return nodeBinDir.resolve(finalCommand).absolutePath
}
afterEvaluate {
tasks {
getByName("frontendProcessResources", Copy::class) {
dependsOn("compileKotlinFrontend")
exclude("**/*.pot")
doLast("Convert PO to JSON") {
destinationDir.walkTopDown().filter {
it.isFile && it.extension == "po"
}.forEach {
exec {
executable = getNodeJsBinaryExecutable()
args(
"$buildDir/js/node_modules/gettext.js/bin/po2json",
it.absolutePath,
"${it.parent}/${it.nameWithoutExtension}.json"
)
println("Converted ${it.name} to ${it.nameWithoutExtension}.json")
}
it.delete()
}
}
}
create("frontendArchive", Jar::class).apply {
dependsOn("frontendBrowserProductionWebpack")
group = "package"
archiveAppendix.set("frontend")
val distribution =
project.tasks.getByName("frontendBrowserProductionWebpack", KotlinWebpack::class).destinationDirectory!!
from(distribution) {
include("*.*")
}
from(webDir)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
into("/assets")
inputs.files(distribution, webDir)
outputs.file(archiveFile)
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Group" to rootProject.group,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis()
)
)
}
}
getByName("backendProcessResources", Copy::class) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
getByName("backendJar").group = "package"
create("jar", Jar::class).apply {
dependsOn("frontendArchive", "backendJar")
group = "package"
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Group" to rootProject.group,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis(),
"Main-Class" to mainClassName
)
)
}
val dependencies = configurations["backendRuntimeClasspath"].filter { it.name.endsWith(".jar") } +
project.tasks["backendJar"].outputs.files +
project.tasks["frontendArchive"].outputs.files
dependencies.forEach {
if (it.isDirectory) from(it) else from(zipTree(it))
}
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
inputs.files(dependencies)
outputs.file(archiveFile)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
create("backendRun", JavaExec::class) {
dependsOn("compileKotlinBackend")
group = "run"
main = mainClassName
classpath =
configurations["backendRuntimeClasspath"] + project.tasks["compileKotlinBackend"].outputs.files +
project.tasks["backendProcessResources"].outputs.files
workingDir = buildDir
}
getByName("compileKotlinBackend") {
dependsOn("compileKotlinMetadata")
}
getByName("compileKotlinFrontend") {
dependsOn("compileKotlinMetadata")
}
}
}
和gradle.properties:
javaVersion=1.8
#Plugins
systemProp.kotlinVersion=1.4.20
systemProp.serializationVersion=1.0.1
#Dependencies
systemProp.kvisionVersion=3.17.2
ktorVersion=1.4.3
commonsCodecVersion=1.10
logbackVersion=1.2.3
kotlin.mpp.stability.nowarn=true
kotlin.js.compiler=legacy
org.gradle.jvmargs=-Xmx2g
和settings.gradle.kts:
pluginManagement {
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://plugins.gradle.org/m2/") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
mavenLocal()
}
resolutionStrategy {
eachPlugin {
when {
requested.id.id == "kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
requested.id.id == "kvision" -> useModule("pl.treksoft:kvision-gradle-plugin:${requested.version}")
}
}
}
}
rootProject.name = "test"
我经常遇到同样的问题。你只需要添加 import:
import kotlinx.serialization.encodeToString
您可能不想手动添加导入,我通常开始输入 encodeToStr
,然后等待建议出现并选择 encodeToString(value: T)
,这将添加所需的导入。
我认为这是一个错误,IDE 没有给您建议导入,而是给我们这个错误。
我正在尝试在多平台 (JVM/JS) 项目中使用 kotlinx.serialization。
当我在公共模块中的某些 class 中向某些数据 class 添加 @Serializable
注释时:
@Serializable
data class User(
val user: String
)
构建成功,没有错误,但看起来 encoders/decoders 没有生成。
在 build/generated-src
中,我没有看到任何提供 encodeToString
扩展功能的相关 kotlin 文件,当我尝试使用类似的东西时:
JSON.encodeToString(User(login = "X"))
我收到未解决的引用错误:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public inline fun <reified T> StringFormat.encodeToString(value: TypeVariable(T)): String defined in kotlinx.serialization
如能帮助解决此问题,我们将不胜感激。提前致谢。
我的build.gradle.kts:
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
/* buildscript {
dependencies {
val kotlinVersion: String by System.getProperties()
classpath(kotlin("serialization", version = kotlinVersion))
}
} */
plugins {
val kotlinVersion: String by System.getProperties()
kotlin("multiplatform") version kotlinVersion
id("kotlinx-serialization") version kotlinVersion
val kvisionVersion: String by System.getProperties()
id("kvision") version kvisionVersion
}
version = "1.0.0-SNAPSHOT"
group = "tech.lorefnon"
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
maven { url = uri("https://repo.spring.io/milestone") }
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
mavenLocal()
}
// Versions
val kotlinVersion: String by System.getProperties()
val kvisionVersion: String by System.getProperties()
val ktorVersion: String by project
val logbackVersion: String by project
val commonsCodecVersion: String by project
val webDir = file("src/frontendMain/web")
val mainClassName = "io.ktor.server.netty.EngineMain"
kotlin {
jvm("backend") {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = listOf("-Xjsr305=strict")
}
}
}
js("frontend") {
browser {
runTask {
outputFileName = "main.bundle.js"
sourceMaps = false
devServer = KotlinWebpackConfig.DevServer(
open = false,
port = 3000,
proxy = mapOf(
"/kv/*" to "http://localhost:8080",
"/login" to "http://localhost:8080",
"/logout" to "http://localhost:8080",
"/kvws/*" to mapOf("target" to "ws://localhost:8080", "ws" to true)
),
contentBase = listOf("$buildDir/processedResources/frontend/main")
)
}
webpackTask {
outputFileName = "main.bundle.js"
}
testTask {
useKarma {
useChromeHeadless()
}
}
}
binaries.executable()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
api("pl.treksoft:kvision-server-ktor:$kvisionVersion")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
}
kotlin.srcDir("build/generated-src/common")
kotlin.srcDir("src/commonMain/kotlin")
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val backendMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
implementation("com.auth0:java-jwt:3.11.0")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-auth:$ktorVersion")
implementation("io.ktor:ktor-auth-jwt:$ktorVersion")
implementation("ch.qos.logback:logback-classic:$logbackVersion")
implementation("commons-codec:commons-codec:$commonsCodecVersion")
implementation("org.redisson:redisson:3.14.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.2")
}
}
val backendTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val frontendMain by getting {
resources.srcDir(webDir)
dependencies {
implementation("pl.treksoft:kvision:$kvisionVersion")
implementation("pl.treksoft:kvision-redux:${kvisionVersion}")
implementation("pl.treksoft:kvision-bootstrap:$kvisionVersion")
implementation("pl.treksoft:kvision-bootstrap-select:$kvisionVersion")
implementation("pl.treksoft:kvision-datacontainer:$kvisionVersion")
implementation("pl.treksoft:kvision-bootstrap-dialog:$kvisionVersion")
implementation("pl.treksoft:kvision-fontawesome:$kvisionVersion")
implementation("pl.treksoft:kvision-i18n:$kvisionVersion")
implementation(npm("redux-logger", "3.0.6"))
}
kotlin.srcDir("build/generated-src/frontend")
}
val frontendTest by getting {
dependencies {
implementation(kotlin("test-js"))
implementation("pl.treksoft:kvision-testutils:$kvisionVersion:tests")
}
}
}
}
fun getNodeJsBinaryExecutable(): String {
val nodeDir = NodeJsRootPlugin.apply(project).nodeJsSetupTaskProvider.get().destination
val isWindows = System.getProperty("os.name").toLowerCase().contains("windows")
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
val command = NodeJsRootPlugin.apply(project).nodeCommand
val finalCommand = if (isWindows && command == "node") "node.exe" else command
return nodeBinDir.resolve(finalCommand).absolutePath
}
afterEvaluate {
tasks {
getByName("frontendProcessResources", Copy::class) {
dependsOn("compileKotlinFrontend")
exclude("**/*.pot")
doLast("Convert PO to JSON") {
destinationDir.walkTopDown().filter {
it.isFile && it.extension == "po"
}.forEach {
exec {
executable = getNodeJsBinaryExecutable()
args(
"$buildDir/js/node_modules/gettext.js/bin/po2json",
it.absolutePath,
"${it.parent}/${it.nameWithoutExtension}.json"
)
println("Converted ${it.name} to ${it.nameWithoutExtension}.json")
}
it.delete()
}
}
}
create("frontendArchive", Jar::class).apply {
dependsOn("frontendBrowserProductionWebpack")
group = "package"
archiveAppendix.set("frontend")
val distribution =
project.tasks.getByName("frontendBrowserProductionWebpack", KotlinWebpack::class).destinationDirectory!!
from(distribution) {
include("*.*")
}
from(webDir)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
into("/assets")
inputs.files(distribution, webDir)
outputs.file(archiveFile)
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Group" to rootProject.group,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis()
)
)
}
}
getByName("backendProcessResources", Copy::class) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
getByName("backendJar").group = "package"
create("jar", Jar::class).apply {
dependsOn("frontendArchive", "backendJar")
group = "package"
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Group" to rootProject.group,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis(),
"Main-Class" to mainClassName
)
)
}
val dependencies = configurations["backendRuntimeClasspath"].filter { it.name.endsWith(".jar") } +
project.tasks["backendJar"].outputs.files +
project.tasks["frontendArchive"].outputs.files
dependencies.forEach {
if (it.isDirectory) from(it) else from(zipTree(it))
}
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
inputs.files(dependencies)
outputs.file(archiveFile)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
create("backendRun", JavaExec::class) {
dependsOn("compileKotlinBackend")
group = "run"
main = mainClassName
classpath =
configurations["backendRuntimeClasspath"] + project.tasks["compileKotlinBackend"].outputs.files +
project.tasks["backendProcessResources"].outputs.files
workingDir = buildDir
}
getByName("compileKotlinBackend") {
dependsOn("compileKotlinMetadata")
}
getByName("compileKotlinFrontend") {
dependsOn("compileKotlinMetadata")
}
}
}
和gradle.properties:
javaVersion=1.8
#Plugins
systemProp.kotlinVersion=1.4.20
systemProp.serializationVersion=1.0.1
#Dependencies
systemProp.kvisionVersion=3.17.2
ktorVersion=1.4.3
commonsCodecVersion=1.10
logbackVersion=1.2.3
kotlin.mpp.stability.nowarn=true
kotlin.js.compiler=legacy
org.gradle.jvmargs=-Xmx2g
和settings.gradle.kts:
pluginManagement {
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://plugins.gradle.org/m2/") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
mavenLocal()
}
resolutionStrategy {
eachPlugin {
when {
requested.id.id == "kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
requested.id.id == "kvision" -> useModule("pl.treksoft:kvision-gradle-plugin:${requested.version}")
}
}
}
}
rootProject.name = "test"
我经常遇到同样的问题。你只需要添加 import:
import kotlinx.serialization.encodeToString
您可能不想手动添加导入,我通常开始输入 encodeToStr
,然后等待建议出现并选择 encodeToString(value: T)
,这将添加所需的导入。
我认为这是一个错误,IDE 没有给您建议导入,而是给我们这个错误。