Files
moshi-companion/docs/guide.md
2025-10-11 16:42:47 +08:00

9.8 KiB

Moshi Companion Documentation

Maven Central

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
    }
}