Files
moshi-companion/docs/guide.md
2025-10-07 08:26:25 +08:00

207 lines
9.8 KiB
Markdown

# Moshi Companion Documentation
![Maven Central](https://img.shields.io/maven-central/v/com.highcapable.moshi.companion/companion-api?logo=apachemaven&logoColor=orange&style=flat-square)
Before you start using it, it is recommended that you read this document carefully so that you can better understand how it works and its functions.
You can find the demo in samples in the root directory of the project, and refer to this document for better use.
## 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 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.
## Quick Start
First, add dependencies to your Android/Kotlin/Java project. We recommend using Gradle's Version Catalog feature to manage dependency versions:
> `gradle/libs.versions.toml`
```toml
[versions]
agp = "8.13.0"
# Kotlin related dependency versions can be obtained from https://kotlinlang.org/docs/releases.html
kotlin = "2.2.20"
ksp = "2.2.20-2.0.3"
# Moshi version is recommended to use the version provided by Moshi GitHub README
moshi = "1.15.2"
moshi-companion = "<version>"
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
[libraries]
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
moshi-companion-api = { module = "com.highcapable.moshi.companion:companion-api", version.ref = "moshi-companion" }
moshi-companion-codegen = { module = "com.highcapable.moshi.companion:companion-codegen", version.ref = "moshi-companion" }
```
Replace the above `<version>` with the latest version number shown at the top.
Then, add the following configuration to your Gradle project configuration file `build.gradle` or `build.gradle.kts` where you need to use Moshi:
```kotlin
plugins {
// For Android projects
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
// KSP
alias(libs.plugins.kotlin.ksp)
}
dependencies {
// Moshi related dependencies
implementation(libs.moshi.kotlin)
ksp(libs.moshi.kotlin.codegen)
// Moshi Companion related dependencies
implementation(libs.moshi.companion.api)
// Moshi Companion Codegen needs to be added after moshi-kotlin-codegen, please note the order
ksp(libs.moshi.companion.codegen)
}
```
Then we need to ensure that Moshi's ProGuard rules generation is disabled, as ProGuard rules are now managed by Moshi Companion.
```kotlin
ksp {
arg("moshi.generateProguardRules", "false")
}
```
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.
## 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.
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
```
Such as:
```
com.highcapable.moshi.companion.r1dd1c7f2a95790d7.generated
```
The class name of `AdapterRegistry` is fixed as `DefaultMoshiAdapterRegistry` and implements the `AdapterRegistry` interface.
When using Moshi, you can very simply use the extension function `addRegistry` to register this generated class to `Moshi.Builder`:
```kotlin
val moshi = Moshi.Builder()
.addRegistry(DefaultMoshiAdapterRegistry())
.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.
## 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`:
```kotlin
ksp {
// Customize the package name of AdapterRegistry, if it's an Android project, it's recommended to directly use "android.namespace"
arg("moshi-companion.generateAdapterRegistryPackageName", "com.yourdomain.yourpackage")
// Customize the class name of AdapterRegistry
arg("moshi-companion.generateAdapterRegistryClassName", "YourCustomMoshiAdapterRegistry")
}
```
Such as:
```
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 need to generate an `AdapterRegistry` that is only accessible to the current project, you can achieve this by adding the following KSP parameter:
```kotlin
ksp {
arg("moshi-companion.generateAdapterRegistryRestrictedAccess", "true")
}
```
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.
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):
```kotlin
ksp {
arg("moshi-companion.proguardRulesKeepEnumClasses", "false")
}
```
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
ksp {
arg("moshi-companion.generateProguardRules", "false")
}
```
## 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.
```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
val adapter = moshi.typeAdapter<List<YourDataClass>>()
// Compare with the original approach
val type = Types.newParameterizedType(List::class.java, YourDataClass::class.java)
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
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`:
```kotlin
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
// Specify the ProGuard rules file generated by Moshi Companion
file("build/generated/ksp/release/resources/META-INF/proguard/").listFiles()?.firstOrNull()?.let {
proguardFiles(it)
}
}
```