diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt index 79fa42de9e2..c6d70477d94 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt @@ -128,8 +128,8 @@ open class NpmProject(@Transient val compilation: KotlinJsCompilation) : Seriali val rootProjectName = project.rootProject.name val localName = if (project != project.rootProject) { - project.name - } else null + (rootProjectName + project.path).replace(":", "-") + } else rootProjectName val targetName = if (target.name.isNotEmpty() && target.name.toLowerCaseAsciiOnly() != "js") { target.name @@ -142,7 +142,6 @@ open class NpmProject(@Transient val compilation: KotlinJsCompilation) : Seriali } else null return sequenceOf( - rootProjectName, localName, targetName, compilationName diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt index af543f40ec8..aed9eb7853c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt @@ -93,15 +93,7 @@ class PackageJson( .setPrettyPrinting() .disableHtmlEscaping() .serializeNulls() - .addSerializationExclusionStrategy( - object : ExclusionStrategy { - override fun shouldSkipField(f: FieldAttributes?): Boolean = - f?.name == this@PackageJson::customFields.name - - override fun shouldSkipClass(clazz: Class<*>?): Boolean = - false - } - ) + .registerTypeAdapterFactory(PackageJsonTypeAdapter()) .create() packageJsonFile.ensureParentDirsCreated() @@ -112,11 +104,6 @@ class PackageJson( } } else null - customFields - .forEach { (key, value) -> - val valueElement = gson.toJsonTree(value) - jsonTree.asJsonObject.add(key, valueElement) - } if (jsonTree != previous) { packageJsonFile.writer().use { gson.toJson(jsonTree, it) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJsonTypeAdapter.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJsonTypeAdapter.kt new file mode 100644 index 00000000000..f79c21157ea --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJsonTypeAdapter.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import com.google.gson.* +import com.google.gson.annotations.SerializedName +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import java.lang.reflect.Field + +// Necessary to get rid of all fields with null value (private, workspace etc) +// But we need to leave customFields because it is user's null value and it is ok +// Here we remove all null-valuable fields, read customFields, add them to jsonObject as elements and remove customFields field +// It helps to not get such json +// { name: "foo", private: null, customFields: { customField1: null }, customField1: null } +// but just +// { name: "foo", customField1: "foo" } +class PackageJsonTypeAdapter : TypeAdapterFactory { + + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + + if (type.rawType != PackageJson::class.java) return null + + fun Field.serializedName() = declaredAnnotations + .filterIsInstance() + .firstOrNull()?.value ?: name + + val declaredFields = type.rawType.declaredFields + + val customFieldsField = declaredFields + .single { it.name == PackageJson::customFields.name } + + return object : TypeAdapter() { + private val delegateAdapter = gson.getDelegateAdapter(this@PackageJsonTypeAdapter, type) + private val elementAdapter = gson.getAdapter(JsonElement::class.java) + + override fun write(writer: JsonWriter, value: T?) { + val jsonObject = delegateAdapter.toJsonTree(value).asJsonObject + + customFieldsField.isAccessible = true + val customFields = customFieldsField.get(value) as Map<*, *> + + customFields + .forEach { (key, value) -> + val valueElement = gson.toJsonTree(value) + key as String + jsonObject.add(key, valueElement) + } + + declaredFields + .map { it.serializedName() } + .filter { jsonObject.get(it) is JsonNull } + .forEach { jsonObject.remove(it) } + + jsonObject.remove(customFieldsField.serializedName()) + + val originalSerializeNulls = writer.serializeNulls + writer.serializeNulls = true + elementAdapter.write(writer, jsonObject) + writer.serializeNulls = originalSerializeNulls + } + + override fun read(reader: JsonReader): T { + return delegateAdapter.read(reader) + } + } + } +} \ No newline at end of file