Moshi Documentation

repository·master·Indexed 27 days ago

https://github.com/square/moshi

A modern JSON library for Java and Kotlin. Moshi provides tools for parsing and serializing JSON, including support for custom type adapters, field mapping via @Json, and alternate type adapters using @JsonQualifier. It offers multiple integration paths for Kotlin, including reflection via KotlinJsonAdapterFactory and compile-time adapter generation using KSP and moshi-kotlin-codegen. The moshi-adapters artifact provides prebuilt JsonAdapters for common types and formats.

Tokens
2.8K
Snippets
12
Records
13
Agent score
91%

What's inside Moshi

  1. Install moshi-adapters

    master

    Add moshi-adapters to your project to access prebuilt JsonAdapters for common types like Rfc3339DateJsonAdapter for java.util.Date.

    <!-- Maven -->
    <dependency>
      <groupId>com.squareup.moshi</groupId>
      <artifactId>moshi-adapters</artifactId>
      <version>latest.version</version>
    </dependency>
    
    <!-- Gradle -->
    implementation 'com.squareup.moshi:moshi-adapters:latest.version'
  2. Configure Kotlin Reflection support

    master

    To use Moshi with Kotlin classes via reflection, you must add the moshi-kotlin dependency and register the KotlinJsonAdapterFactory.

    Note: The reflection adapter requires the kotlin-reflect library. For better performance and to avoid the kotlin-reflect dependency, use Codegen instead.

    1. Add dependency:

    implementation("com.squareup.moshi:moshi-kotlin:1.15.2")

    2. Register factory:

    val moshi = Moshi.Builder()
        .addLast(KotlinJsonAdapterFactory())
        .build()
  3. Install Moshi

    master

    To use Moshi in your project, add the dependency via Maven or Gradle.

    Maven

    <dependency>
      <groupId>com.squareup.moshi</groupId>
      <artifactId>moshi</artifactId>
      <version>1.15.2</version>
    </dependency>

    Gradle

    implementation("com.squareup.moshi:moshi:1.15.2")
  4. Parse and Serialize JSON in Java

    master

    Use Moshi.Builder to create a Moshi instance, then obtain a JsonAdapter for your target class to perform JSON operations.

    Parsing JSON to an object:

    String json = ...;
    Moshi moshi = new Moshi.Builder().build();
    JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
    BlackjackHand blackjackHand = jsonAdapter.fromJson(json);

    Serializing an object to JSON:

    BlackjackHand blackjackHand = ...;
    Moshi moshi = new Moshi.Builder().build();
    JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
    String json = jsonAdapter.toJson(blackjackHand);
    String json = ...;
    
    Moshi moshi = new Moshi.Builder().build();
    JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
    
    BlackjackHand blackjackHand = jsonAdapter.fromJson(json);
    System.out.println(blackjackHand);
  5. Parse and Serialize JSON in Kotlin

    master

    Use Moshi.Builder to create a Moshi instance. For Kotlin, it is recommended to use either Kotlin code generation or the KotlinJsonAdapterFactory for reflection.

    Parsing JSON to an object:

    val json: String = ...
    val moshi: Moshi = Moshi.Builder().build()
    val jsonAdapter: JsonAdapter<BlackjackHand> = moshi.adapter<BlackjackHand>()
    val blackjackHand = jsonAdapter.fromJson(json)

    Serializing an object to JSON:

    val blackjackHand = ...
    val moshi: Moshi = Moshi.Builder().build()
    val jsonAdapter: JsonAdapter<BlackjackHand> = moshi.adapter<BlackjackHand>()
    val json: String = jsonAdapter.toJson(blackjackHand)
    val json: String = ...
    
    val moshi: Moshi = Moshi.Builder().build()
    val jsonAdapter: JsonAdapter<BlackjackHand> = moshi.adapter<BlackjackHand>()
    
    val blackjackHand = jsonAdapter.fromJson(json)
    println(blackjackHand)
  6. Create a Custom Type Adapter

    master

    A type adapter is a class containing methods annotated with @ToJson and @FromJson. This allows you to customize how specific types are converted to and from JSON.

    Example: Compact Card Representation Instead of a verbose object, convert a Card to a string like "4H" (4 of Hearts).

    class CardAdapter {
      @ToJson fun toJson(card: Card): String {
        return card.rank + card.suit.name.substring(0, 1)
      }
    
      @FromJson fun fromJson(card: String): Card {
        if (card.length != 2) throw JsonDataException("Unknown card: $card")
        val rank = card[0]
        return when (card[1]) {
          'C' -> Card(rank, Suit.CLUBS)
          'D' -> Card(rank, Suit.DIAMONDS)
          'H' -> Card(rank, Suit.HEARTS)
          'S' -> Card(rank, Suit.SPADES)
          else -> throw JsonDataException("unknown suit: $card")
        }
      }
    }

    Register the adapter:

    val moshi = Moshi.Builder()
        .add(CardAdapter())
        .build()
    class CardAdapter {
      @ToJson fun toJson(card: Card): String {
        return card.rank + card.suit.name.substring(0, 1)
      }
    
      @FromJson fun fromJson(card: String): Card {
        if (card.length != 2) throw JsonDataException("Unknown card: $card")
    
        val rank = card[0]
        return when (card[1]) {
          'C' -> Card(rank, Suit.CLUBS)
          'D' -> Card(rank, Suit.DIAMONDS)
          'H' -> Card(rank, Suit.HEARTS)
          'S' -> Card(rank, Suit.SPADES)
          else -> throw JsonDataException("unknown suit: $card")
        }
      }
    }
  7. Use @JsonQualifier for alternate type adapters

    master

    Use @JsonQualifier to apply a specific type adapter to certain fields of a type without changing the encoding for all instances of that type. This is useful when the same type (e.g., Int) needs different JSON representations (e.g., a decimal number vs. a hex color string).

    1. Define the qualifier:

    @Retention(RUNTIME)
    @JsonQualifier
    annotation class HexColor

    2. Apply to the field:

    class Rectangle(
      val width: Int,
      val height: Int,
      @HexColor val color: Int
    )

    3. Create the adapter using the qualifier:

    class ColorAdapter {
      @ToJson fun toJson(@HexColor rgb: Int): String {
        return "#%06x".format(rgb)
      }
    
      @FromJson @HexColor fun fromJson(rgb: String): Int {
        return rgb.substring(1).toInt(16)
      }
    }

    4. Register the adapter:

    val moshi = Moshi.Builder()
        .add(ColorAdapter())
        .build()
    @Retention(RUNTIME)
    @JsonQualifier
    annotation class HexColor
    
    class Rectangle(
      val width: Int,
      val height: Int,
      @HexColor val color: Int
    )
    
    class ColorAdapter {
      @ToJson fun toJson(@HexColor rgb: Int): String {
        return "#%06x".format(rgb)
      }
    
      @FromJson @HexColor fun fromJson(rgb: String): Int {
        return rgb.substring(1).toInt(16)
      }
    }
  8. Enable Kotlin Codegen with KSP

    master

    Moshi's Kotlin codegen uses Kotlin Symbol Processing (KSP) to generate fast, compile-time adapters for your classes. This is preferred over reflection.

    1. Annotate your classes:

    @JsonClass(generateAdapter = true)
    data class BlackjackHand(
      val hidden_card: Card,
      val visible_cards: List<Card>
    )

    2. Configure KSP in your build file:

    plugins {
      id("com.google.devtools.ksp") version "2.3.4" // Or latest
    }
    
    dependencies {
      ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2")
    }
  9. Cut a release for Moshi

    master

    To release a new version of Moshi, follow these steps to update the changelog, set the version variables, update the project files, and tag the release in Git. This process triggers a GitHub Action workflow that creates a GitHub release and uploads artifacts to Maven Central.

    1. Update CHANGELOG.md:
      • Change the Unreleased header to the new release version.
      • Add a link URL to the header.
      • Add a new Unreleased section at the top.
    2. Set version environment variables:
      • RELEASE_VERSION: The version being released (e.g., 1.15.2).
      • NEXT_VERSION: The version for the next development cycle (e.g., 1.15.3-SNAPSHOT).
    3. Execute version updates and tagging using the commands provided below.
    # 1. Set version variables
    export RELEASE_VERSION=X.Y.Z
    export NEXT_VERSION=X.Y.Z-SNAPSHOT
    
    # 2. Update gradle.properties and README.md files
    sed -i "" \
      "s/VERSION_NAME=.*/VERSION_NAME=$RELEASE_VERSION/g" \
      gradle.properties
    sed -i "" \
      "s/\"com.squareup.moshi:\([^\:]*\):[0-9.]*\"/\"com.squareup.moshi:\1:$RELEASE_VERSION\"/g" \
      `find . -name "README.md"`
    
    # 3. Commit and tag the release
    git commit -am "Prepare version $RELEASE_VERSION."
    git tag -am "Version $RELEASE_VERSION" $RELEASE_VERSION
    
    # 4. Prepare the next development version
    sed -i "" \
      "s/VERSION_NAME=.*/VERSION_NAME=$NEXT_VERSION/g" \
      gradle.properties
    git commit -am "Prepare next development version."
    
    # 5. Push changes and tags
    git push && git push --tags
  10. Configure R8 / ProGuard for Moshi

    master

    If you are using reflective serialization with R8 or ProGuard, you must add keep rules for your serialized classes.

    For Enums, annotate them with @JsonClass(generateAdapter = false) to prevent them from being removed or obfuscated by R8/ProGuard.

    @JsonClass(generateAdapter = false)
    enum class Suit {
      CLUBS, DIAMONDS, HEARTS, SPADES
    }
  11. Use prebuilt Moshi JsonAdapters

    master

    To use a prebuilt adapter, supply an instance of the desired adapter to the Moshi.Builder using the .add(Class, JsonAdapter) method before calling .build().

    Moshi moshi = new Moshi.Builder()
        .add(Date.class, new Rfc3339DateJsonAdapter())
        //etc
        .build();
  12. Omit fields from JSON with @Json(ignore = true)

    master

    To prevent a field from being included in JSON during serialization or being read from JSON during deserialization, annotate it with @Json(ignore = true).

    In Kotlin, ignored fields in a primary constructor must have a default value.

    class BlackjackHand(
      @Json(ignore = true) var total: Int = 0,
      val hidden_card: Card,
      val visible_cards: List<Card>
    )