8.9 KiB
Moshi Companion Documentation
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. 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
[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:
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.
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:
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:
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:
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):
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):
ksp {
arg("moshi-companion.generateProguardRules", "false")
}
Extension API
Moshi Companion provides the typeAdapter extension feature to simplify Types.newParameterizedType.
The core content is implemented using the TypeRef feature of the KavaRef project.
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)
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:
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
}
}