refactor: remove TypeRef

This commit is contained in:
2025-12-13 22:50:20 +08:00
parent 187b759afd
commit 44f4ee9a99
7 changed files with 56 additions and 172 deletions

View File

@@ -21,6 +21,7 @@
*/ */
package com.highcapable.moshi.companion.api package com.highcapable.moshi.companion.api
import com.highcapable.kavaref.extension.typeRef
import com.highcapable.moshi.companion.api.MoshiCompanion.AdapterRegistry import com.highcapable.moshi.companion.api.MoshiCompanion.AdapterRegistry
import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi

View File

@@ -1,96 +0,0 @@
/*
* Moshi Companion - Companion to Moshi with more practical features.
* Copyright (C) 2019 HighCapable
* https://github.com/HighCapable/moshi-companion
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is created by fankes on 2025/10/6.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.moshi.companion.api
import com.highcapable.kavaref.extension.classOf
import com.highcapable.kavaref.extension.toClass
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
/**
* Type reference class for getting generic parameter [T] type.
*
* The purpose of this class is to retain erased generics at runtime.
* @see typeRef
*/
abstract class TypeRef<T : Any> {
/**
* Get the generic parameter [T] type.
* @return [Type]
*/
val type by lazy {
when (val superclass = javaClass.genericSuperclass) {
is ParameterizedType ->
if (superclass.rawType == classOf<TypeRef<*>>())
superclass.actualTypeArguments.firstOrNull() ?: error("Type argument cannot be null.")
else error("Must only create direct subclasses of TypeRef.")
classOf<TypeRef<*>>() -> error("TypeRef must be created with a type argument: object : TypeRef<...>() {}.")
else -> error("Must only create direct subclasses of TypeRef.")
}
}
/**
* Get the raw class type of the generic parameter [T].
* @return [Class]
*/
val rawType by lazy {
when (val currentType = type) {
is Class<*> -> currentType
is ParameterizedType -> currentType.toClass()
else -> classOf<Any>()
}
}
/**
* Checks if the specified [other] type can be assigned to this type.
* @param other the type to check.
* @return `true` if the specified type can be assigned to this type, `false` otherwise.
*/
fun isAssignableFrom(other: Type) = when {
type is Class<*> && other is Class<*> -> rawType.isAssignableFrom(other)
else -> type == other
}
override fun toString() = type.toString()
override fun equals(other: Any?) = other is TypeRef<*> && type == other.type
override fun hashCode() = type.hashCode()
}
/**
* Create a [TypeRef] instance with the reified type parameter [T].
*
* Usage:
*
* ```kotlin
* val typeRef = typeRef<List<String>>()
* // This will be of type `List<String>`.
* val type = typeRef.type
* // This will be of type `List`.
* val rawType = typeRef.rawType
* ```
* @see TypeRef
* @return [TypeRef]<[T]>
*/
inline fun <reified T : Any> typeRef() = object : TypeRef<T>() {}

View File

@@ -41,8 +41,5 @@ object DeclaredSymbol {
const val MOSHI_COMPANION_CLASS_NAME = "MoshiCompanion" const val MOSHI_COMPANION_CLASS_NAME = "MoshiCompanion"
const val MOSHI_COMPANION_CLASS = "$MOSHI_COMPANION_API_PACKAGE_NAME.$MOSHI_COMPANION_CLASS_NAME" const val MOSHI_COMPANION_CLASS = "$MOSHI_COMPANION_API_PACKAGE_NAME.$MOSHI_COMPANION_CLASS_NAME"
const val TYPE_REF_CLASS_NAME = "TypeRef"
const val TYPE_REF_CLASS = "$MOSHI_COMPANION_API_PACKAGE_NAME.$TYPE_REF_CLASS_NAME"
const val ADAPTER_REGISTRY_CLASS_NAME = "AdapterRegistry" const val ADAPTER_REGISTRY_CLASS_NAME = "AdapterRegistry"
} }

View File

@@ -273,16 +273,6 @@ class AdapterRegistryGenerator(override val environment: SymbolProcessorEnvironm
appendLine("-keep,allowobfuscation @${DeclaredSymbol.JSON_ANNOTATION_CLASS} class *") appendLine("-keep,allowobfuscation @${DeclaredSymbol.JSON_ANNOTATION_CLASS} class *")
appendLine() appendLine()
// Keep the `TypeRef` class and its subclasses.
appendLine("-keep,allowobfuscation class ${DeclaredSymbol.TYPE_REF_CLASS} {")
appendLine(" <fields>;")
appendLine(" <methods>;")
appendLine("}")
appendLine()
appendLine("-keep,allowobfuscation class * extends ${DeclaredSymbol.TYPE_REF_CLASS}")
appendLine()
// Keep generic signatures. // Keep generic signatures.
appendLine("-keepattributes Signature") appendLine("-keepattributes Signature")
appendLine() appendLine()

View File

@@ -8,9 +8,11 @@
## 开始之前 ## 开始之前
此项目的主要功能是为 [Moshi](https://github.com/square/moshi) 提供伴侣功能,核心功能依赖于 Moshi 项目核心完成,你需要使用 Moshi 的 `moshi-kotlin` `moshi-kotlin-codegen` 依赖来生成适配器类。 此项目的主要功能是为 [Moshi](https://github.com/square/moshi) 提供伴侣功能,核心功能依赖于 Moshi 项目核心完成,你需要使用 Moshi 的 `moshi-kotlin`
`moshi-kotlin-codegen` 依赖来生成适配器类。
此项目的目的是将 `moshi-kotlin-codegen` 生成的适配器类生成 "实体类 → 适配器类" 的映射注册到 `AdapterRegistry` 中并创建自定义的 `JsonAdapter` 设置到 `Moshi.Builder`,从而避免 Moshi 通过 `Class.forName` 反射查找适配器类,达到完全混淆实体类名称和字段的目的,同时性能将由 O(n) 提升到 O(1)。 此项目的目的是将 `moshi-kotlin-codegen` 生成的适配器类生成 "实体类 → 适配器类" 的映射注册到 `AdapterRegistry` 中并创建自定义的 `JsonAdapter` 设置到
`Moshi.Builder`,从而避免 Moshi 通过 `Class.forName` 反射查找适配器类,达到完全混淆实体类名称和字段的目的,同时性能将由 O(n) 提升到 O(1)。
此项目主要专注于 Android 项目,在纯 Kotlin/Java 项目中依然可以使用。 此项目主要专注于 Android 项目,在纯 Kotlin/Java 项目中依然可以使用。
@@ -78,13 +80,16 @@ ksp {
如果你没有关闭 Moshi 的混淆规则生成功能Moshi Companion 将会在编译时抛出异常提示你进行修正。 如果你没有关闭 Moshi 的混淆规则生成功能Moshi Companion 将会在编译时抛出异常提示你进行修正。
至此,你已经完成了 Moshi Companion 的依赖添加和配置,如果你是在使用 Moshi 的现有项目中添加 Moshi Companion你只需要引入上述步骤中的 `moshi-companion-api``moshi-companion-codegen` 依赖,并添加关闭混淆规则生成的配置即可。 至此,你已经完成了 Moshi Companion 的依赖添加和配置,如果你是在使用 Moshi 的现有项目中添加 Moshi Companion你只需要引入上述步骤中的
`moshi-companion-api``moshi-companion-codegen` 依赖,并添加关闭混淆规则生成的配置即可。
## 注册适配器 ## 注册适配器
Moshi Companion 通过读取项目中所有使用 `@JsonClass(generateAdapter = true)` 注解的类来生成 `AdapterRegistry`,你需要在运行时通过 `Moshi.Builder` 手动注册这些适配器。 Moshi Companion 通过读取项目中所有使用 `@JsonClass(generateAdapter = true)` 注解的类来生成 `AdapterRegistry`,你需要在运行时通过 `Moshi.Builder`
手动注册这些适配器。
在执行过一次 Gradle 构建后Moshi Companion 会在项目的 `build/generated/ksp` 目录下生成 `AdapterRegistry` 类,生成的默认格式为扫描到不重复的一个存在 `@JsonClass(generateAdapter = true)` 注解的包名,使用这个包名转换为 16 位的 Hash 作为当前模块的唯一标识,并生成如下格式的包名: 在执行过一次 Gradle 构建后Moshi Companion 会在项目的 `build/generated/ksp` 目录下生成 `AdapterRegistry` 类,生成的默认格式为扫描到不重复的一个存在
`@JsonClass(generateAdapter = true)` 注解的包名,使用这个包名转换为 16 位的 Hash 作为当前模块的唯一标识,并生成如下格式的包名:
``` ```
com.highcapable.moshi.companion.r + 16 位 Hash + generated com.highcapable.moshi.companion.r + 16 位 Hash + generated
@@ -161,23 +166,8 @@ ksp {
## 扩展 API ## 扩展 API
Moshi Companion 提供了 `TypeRef`来简化 `Types.newParameterizedType` 的使用,它的启发和实践来自老牌 [Gson](https://github.com/google/gson) 项目的 `TypeToken`,你可以通过继承 `TypeRef` 来创建一个类型引用,然后通过 `type` 属性获取 `Type` 对象。 Moshi Companion 提供了 `typeAdapter` 扩展函数来简化 `Types.newParameterizedType`
的使用,核心内容使用了 [KavaRef](https://github.com/HighCapable/KavaRef) 项目的 `TypeRef` 功能来实现。
```kotlin
val typeRef = typeRef<List<YourDataClass>>()
// 获取 Type 对象,即 List<YourDataClass>
val type = typeRef.type
// 获取原始对象,即 List
val rawType = typeRef.rawType
```
你可以直接将获取到的 `type` 对象传递给 `Moshi.adapter` 方法来获取对应的 `JsonAdapter`
```kotlin
val adapter = moshi.adapter<List<YourDataClass>>(typeRef.type)
```
当然,你也可以不需要写的这么复杂,你可以直接使用 Moshi Companion 提供的 `typeAdapter` 扩展函数来简化这个过程:
```kotlin ```kotlin
val adapter = moshi.typeAdapter<List<YourDataClass>>() val adapter = moshi.typeAdapter<List<YourDataClass>>()
@@ -186,13 +176,13 @@ val type = Types.newParameterizedType(List::class.java, YourDataClass::class.jav
val adapter = moshi.adapter<List<YourDataClass>>(type) val adapter = moshi.adapter<List<YourDataClass>>(type)
``` ```
`TypeRef` 已经在 Moshi Companion 默认生成的混淆规则中被处理,完全不受 R8 的混淆和优化影响,同样地,泛型类的类名可以完全做到安全混淆、压缩体积、减少反射和暴露风险。
## 故障排查 ## 故障排查
如果你没有禁用 Moshi Companion 的混淆规则生成,但是混淆规则没有被加入到 `shrink-rules`,你可以在 R8 结束后检查生成的 `configuration.txt` 文件,查看是否包含 "JsonAdapter"。 如果你没有禁用 Moshi Companion 的混淆规则生成,但是混淆规则没有被加入到 `shrink-rules`,你可以在 R8 结束后检查生成的 `configuration.txt`
文件,查看是否包含 "JsonAdapter"。
目前在 Android 项目中这个问题可能出现在项目主模块 (例如 "app"),如果混淆规则没生效,请查看 `build/generated/ksp/release/resources/META-INF/proguard/` 下有没有规则文件,如果存在,那么请在 `build.gradle.kts``buildTypes` 中添加如下配置: 目前在 Android 项目中这个问题可能出现在项目主模块 (例如 "app"),如果混淆规则没生效,请查看 `build/generated/ksp/release/resources/META-INF/proguard/`
下有没有规则文件,如果存在,那么请在 `build.gradle.kts``buildTypes` 中添加如下配置:
```kotlin ```kotlin
release { release {

View File

@@ -8,9 +8,12 @@ You can find the demo in samples in the root directory of the project, and refer
## Before You Begin ## Before You Begin
The main function of this project is to provide companion features for [Moshi](https://github.com/square/moshi). The core functionality depends on the Moshi project core. You need to use Moshi's `moshi-kotlin` and `moshi-kotlin-codegen` dependencies to generate adapter classes. The main function of this project is to provide companion features for [Moshi](https://github.com/square/moshi). The core functionality depends on the
Moshi project core. You need to use Moshi's `moshi-kotlin` and `moshi-kotlin-codegen` dependencies to generate adapter classes.
The purpose of this project is to generate "Entity Class → Adapter Class" mappings for the adapter classes generated by `moshi-kotlin-codegen` and register them to the `AdapterRegistry`, then create a custom `JsonAdapter` and set it to the `Moshi.Builder`. This avoids Moshi using `Class.forName` reflection to find adapter classes, achieving complete obfuscation of entity class names and fields, while improving performance from O(n) to O(1). The purpose of this project is to generate "Entity Class → Adapter Class" mappings for the adapter classes generated by `moshi-kotlin-codegen` and
register them to the `AdapterRegistry`, then create a custom `JsonAdapter` and set it to the `Moshi.Builder`. This avoids Moshi using `Class.forName`
reflection to find adapter classes, achieving complete obfuscation of entity class names and fields, while improving performance from O(n) to O(1).
This project primarily focuses on Android projects but can still be used in pure Kotlin/Java projects. This project primarily focuses on Android projects but can still be used in pure Kotlin/Java projects.
@@ -78,13 +81,18 @@ ksp {
If you don't disable Moshi's ProGuard rules generation, Moshi Companion will throw an exception at compile time prompting you to make corrections. If you don't disable Moshi's ProGuard rules generation, Moshi Companion will throw an exception at compile time prompting you to make corrections.
At this point, you have completed the dependency addition and configuration of Moshi Companion. If you are adding Moshi Companion to an existing project that uses Moshi, you only need to introduce the `moshi-companion-api` and `moshi-companion-codegen` dependencies from the steps above and add the configuration to disable ProGuard rules generation. At this point, you have completed the dependency addition and configuration of Moshi Companion. If you are adding Moshi Companion to an existing
project that uses Moshi, you only need to introduce the `moshi-companion-api` and `moshi-companion-codegen` dependencies from the steps above and add
the configuration to disable ProGuard rules generation.
## Registering Adapters ## Registering Adapters
Moshi Companion generates `AdapterRegistry` by reading all classes in the project that use the `@JsonClass(generateAdapter = true)` annotation. You need to manually register these adapters through `Moshi.Builder` at runtime. Moshi Companion generates `AdapterRegistry` by reading all classes in the project that use the `@JsonClass(generateAdapter = true)` annotation. You
need to manually register these adapters through `Moshi.Builder` at runtime.
After performing a Gradle build, Moshi Companion will generate an `AdapterRegistry` class in the `build/generated/ksp` directory of the project. The default generated format uses a unique package name found with the `@JsonClass(generateAdapter = true)` annotation, converts this package name to a 16-character hash as the unique identifier for the current module, and generates a package name in the following format: After performing a Gradle build, Moshi Companion will generate an `AdapterRegistry` class in the `build/generated/ksp` directory of the project. The
default generated format uses a unique package name found with the `@JsonClass(generateAdapter = true)` annotation, converts this package name to a
16-character hash as the unique identifier for the current module, and generates a package name in the following format:
``` ```
com.highcapable.moshi.companion.r + 16-character hash + generated com.highcapable.moshi.companion.r + 16-character hash + generated
@@ -106,11 +114,13 @@ val moshi = Moshi.Builder()
.build() .build()
``` ```
Then, you can happily continue using Moshi for JSON serialization and deserialization, completely unaffected by R8's obfuscation and optimization. Class names can be safely obfuscated, reducing size, minimizing reflection, and reducing exposure risks. Then, you can happily continue using Moshi for JSON serialization and deserialization, completely unaffected by R8's obfuscation and optimization.
Class names can be safely obfuscated, reducing size, minimizing reflection, and reducing exposure risks.
## Advanced Usage ## Advanced Usage
If you need to customize the class name and package name of `AdapterRegistry`, you can achieve this by adding the following KSP parameters in `build.gradle` or `build.gradle.kts`: If you need to customize the class name and package name of `AdapterRegistry`, you can achieve this by adding the following KSP parameters in
`build.gradle` or `build.gradle.kts`:
```kotlin ```kotlin
ksp { ksp {
@@ -127,9 +137,11 @@ Such as:
com.yourdomain.yourpackage.generated.YourCustomMoshiAdapterRegistry com.yourdomain.yourpackage.generated.YourCustomMoshiAdapterRegistry
``` ```
If you maintain a modular project focused on data models, we recommend fixing the generated `AdapterRegistry` package name and class name as shown in the example above to prevent automatically generated content from not meeting your project requirements. If you maintain a modular project focused on data models, we recommend fixing the generated `AdapterRegistry` package name and class name as shown in
the example above to prevent automatically generated content from not meeting your project requirements.
If you need to generate an `AdapterRegistry` that is only accessible to the current project, you can achieve this by adding the following KSP parameter: If you need to generate an `AdapterRegistry` that is only accessible to the current project, you can achieve this by adding the following KSP
parameter:
```kotlin ```kotlin
ksp { ksp {
@@ -137,11 +149,14 @@ ksp {
} }
``` ```
This way, the generated `AdapterRegistry` class will be set to `internal` and can only be accessed within the current module, avoiding misuse or abuse by other projects. This way, the generated `AdapterRegistry` class will be set to `internal` and can only be accessed within the current module, avoiding misuse or abuse
by other projects.
Moshi Companion's automatically generated ProGuard rules include obfuscation protection for Enum class key values. By default, only class names are obfuscated. Moshi Companion's automatically generated ProGuard rules include obfuscation protection for Enum class key values. By default, only class names are
obfuscated.
Please note that Moshi's default behavior is to not obfuscate Enum classes only when they are annotated with `@JsonClass(generateAdapter = false)`. When using Moshi Companion, all Enum classes will be protected from obfuscation. Please note that Moshi's default behavior is to not obfuscate Enum classes only when they are annotated with `@JsonClass(generateAdapter = false)`.
When using Moshi Companion, all Enum classes will be protected from obfuscation.
If you don't need this feature, you can disable it by adding the following KSP parameter (not recommended to disable): If you don't need this feature, you can disable it by adding the following KSP parameter (not recommended to disable):
@@ -151,7 +166,8 @@ ksp {
} }
``` ```
If you don't want Moshi Companion to automatically generate any ProGuard rules, you can also disable it by adding the following KSP parameter (not recommended to disable): If you don't want Moshi Companion to automatically generate any ProGuard rules, you can also disable it by adding the following KSP parameter (not
recommended to disable):
```kotlin ```kotlin
ksp { ksp {
@@ -161,23 +177,8 @@ ksp {
## Extension API ## Extension API
Moshi Companion provides a `TypeRef` class to simplify the use of `Types.newParameterizedType`. Its inspiration and practice come from the veteran [Gson](https://github.com/google/gson) project's `TypeToken`. You can create a type reference by extending `TypeRef`, then get the `Type` object through the `type` property. Moshi Companion provides the `typeAdapter` extension feature to simplify `Types.newParameterizedType`.
The core content is implemented using the `TypeRef` feature of the [KavaRef](https://github.com/HighCapable/KavaRef) project.
```kotlin
val typeRef = typeRef<List<YourDataClass>>()
// Get the Type object, i.e., List<YourDataClass>
val type = typeRef.type
// Get the raw object, i.e., List
val rawType = typeRef.rawType
```
You can directly pass the obtained `type` object to the `Moshi.adapter` method to get the corresponding `JsonAdapter`.
```kotlin
val adapter = moshi.adapter<List<YourDataClass>>(typeRef.type)
```
Of course, you don't need to write it so complexly. You can directly use the `typeAdapter` extension function provided by Moshi Companion to simplify this process:
```kotlin ```kotlin
val adapter = moshi.typeAdapter<List<YourDataClass>>() val adapter = moshi.typeAdapter<List<YourDataClass>>()
@@ -186,13 +187,14 @@ val type = Types.newParameterizedType(List::class.java, YourDataClass::class.jav
val adapter = moshi.adapter<List<YourDataClass>>(type) val adapter = moshi.adapter<List<YourDataClass>>(type)
``` ```
`TypeRef` has been handled in the ProGuard rules generated by default by Moshi Companion and is completely unaffected by R8's obfuscation and optimization. Similarly, generic class names can be safely obfuscated, reducing size, minimizing reflection, and reducing exposure risks.
## Troubleshooting ## Troubleshooting
If you haven't disabled Moshi Companion's ProGuard rules generation, but the ProGuard rules are not included in `shrink-rules`, you can check the generated `configuration.txt` file after R8 finishes to see if it contains "JsonAdapter". If you haven't disabled Moshi Companion's ProGuard rules generation, but the ProGuard rules are not included in `shrink-rules`, you can check the
generated `configuration.txt` file after R8 finishes to see if it contains "JsonAdapter".
Currently in Android projects, this issue may occur in the main project module (e.g., "app"). If the ProGuard rules don't take effect, please check if there are rule files under `build/generated/ksp/release/resources/META-INF/proguard/`. If they exist, please add the following configuration in the `buildTypes` section of `build.gradle.kts`: Currently in Android projects, this issue may occur in the main project module (e.g., "app"). If the ProGuard rules don't take effect, please check if
there are rule files under `build/generated/ksp/release/resources/META-INF/proguard/`. If they exist, please add the following configuration in the
`buildTypes` section of `build.gradle.kts`:
```kotlin ```kotlin
release { release {

View File

@@ -5,7 +5,7 @@ ksp = "2.0.10-1.0.24"
auto-service-annotations = "1.1.1" auto-service-annotations = "1.1.1"
auto-service-ksp = "1.2.0" auto-service-ksp = "1.2.0"
kavaref-core = "1.0.2" kavaref-core = "1.0.2"
kavaref-extension = "1.0.1" kavaref-extension = "1.0.2"
kotlinpoet = "2.2.0" kotlinpoet = "2.2.0"
moshi = "1.15.2" moshi = "1.15.2"
maven-publish = "0.34.0" maven-publish = "0.34.0"