9.8 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 a TypeRef
class to simplify the use of Types.newParameterizedType
. Its inspiration and practice come from the veteran Gson project's TypeToken
. You can create a type reference by extending TypeRef
, then get the Type
object through the type
property.
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
.
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:
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
:
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
}
}