mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Update README.md to include Kotlin Examples (#1355)
This commit is contained in:
461
README.md
461
README.md
@@ -1,8 +1,13 @@
|
|||||||
Moshi
|
Moshi
|
||||||
=====
|
=====
|
||||||
|
|
||||||
Moshi is a modern JSON library for Android and Java. It makes it easy to parse JSON into Java
|
Moshi is a modern JSON library for Android, Java and Kotlin. It makes it easy to parse JSON into Java and Kotlin
|
||||||
objects:
|
classes:
|
||||||
|
|
||||||
|
_Note: The Kotlin examples of this README assume use of either Kotlin code gen or `KotlinJsonAdapterFactory` for reflection. Plain Java-based reflection is unsupported on Kotlin classes._
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String json = ...;
|
String json = ...;
|
||||||
@@ -13,8 +18,26 @@ JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
|
|||||||
BlackjackHand blackjackHand = jsonAdapter.fromJson(json);
|
BlackjackHand blackjackHand = jsonAdapter.fromJson(json);
|
||||||
System.out.println(blackjackHand);
|
System.out.println(blackjackHand);
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
And it can just as easily serialize Java objects as JSON:
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val json: String = ...
|
||||||
|
|
||||||
|
val moshi: Moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter: JsonAdapter<BlackjackHand> = moshi.adapter<BlackjackHand>()
|
||||||
|
|
||||||
|
val blackjackHand = jsonAdapter.fromJson(json)
|
||||||
|
println(blackjackHand)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
And it can just as easily serialize Java or Kotlin objects as JSON:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
BlackjackHand blackjackHand = new BlackjackHand(
|
BlackjackHand blackjackHand = new BlackjackHand(
|
||||||
@@ -27,6 +50,24 @@ JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
|
|||||||
String json = jsonAdapter.toJson(blackjackHand);
|
String json = jsonAdapter.toJson(blackjackHand);
|
||||||
System.out.println(json);
|
System.out.println(json);
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val blackjackHand = BlackjackHand(
|
||||||
|
Card('6', SPADES),
|
||||||
|
listOf(Card('4', CLUBS), Card('A', HEARTS))
|
||||||
|
)
|
||||||
|
|
||||||
|
val moshi: Moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter: JsonAdapter<BlackjackHand> = moshi.adapter<BlackjackHand>()
|
||||||
|
|
||||||
|
val json: String = jsonAdapter.toJson(blackjackHand)
|
||||||
|
println(json)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Built-in Type Adapters
|
### Built-in Type Adapters
|
||||||
|
|
||||||
@@ -40,6 +81,9 @@ Moshi has built-in support for reading and writing Java’s core data types:
|
|||||||
It supports your model classes by writing them out field-by-field. In the example above Moshi uses
|
It supports your model classes by writing them out field-by-field. In the example above Moshi uses
|
||||||
these classes:
|
these classes:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class BlackjackHand {
|
class BlackjackHand {
|
||||||
public final Card hidden_card;
|
public final Card hidden_card;
|
||||||
@@ -57,6 +101,30 @@ enum Suit {
|
|||||||
CLUBS, DIAMONDS, HEARTS, SPADES;
|
CLUBS, DIAMONDS, HEARTS, SPADES;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class BlackjackHand(
|
||||||
|
val hidden_card: Card,
|
||||||
|
val visible_cards: List<Card>,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
|
||||||
|
class Card(
|
||||||
|
val rank: Char,
|
||||||
|
val suit: Suit
|
||||||
|
...
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Suit {
|
||||||
|
CLUBS, DIAMONDS, HEARTS, SPADES;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
to read and write this JSON:
|
to read and write this JSON:
|
||||||
|
|
||||||
@@ -91,6 +159,9 @@ suit in separate fields: `{"rank":"A","suit":"HEARTS"}`. With a type adapter, we
|
|||||||
encoding to something more compact: `"4H"` for the four of hearts or `"JD"` for the jack of
|
encoding to something more compact: `"4H"` for the four of hearts or `"JD"` for the jack of
|
||||||
diamonds:
|
diamonds:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class CardAdapter {
|
class CardAdapter {
|
||||||
@ToJson String toJson(Card card) {
|
@ToJson String toJson(Card card) {
|
||||||
@@ -111,14 +182,54 @@ class CardAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Register the type adapter with the `Moshi.Builder` and we’re good to go.
|
Register the type adapter with the `Moshi.Builder` and we’re good to go.
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Moshi moshi = new Moshi.Builder()
|
Moshi moshi = new Moshi.Builder()
|
||||||
.add(new CardAdapter())
|
.add(new CardAdapter())
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val moshi = Moshi.Builder()
|
||||||
|
.add(CardAdapter())
|
||||||
|
.build()
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Voilà:
|
Voilà:
|
||||||
|
|
||||||
@@ -154,17 +265,35 @@ We would like to combine these two fields into one string to facilitate the date
|
|||||||
later point. Also, we would like to have all variable names in CamelCase. Therefore, the `Event`
|
later point. Also, we would like to have all variable names in CamelCase. Therefore, the `Event`
|
||||||
class we want Moshi to produce like this:
|
class we want Moshi to produce like this:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class Event {
|
class Event {
|
||||||
String title;
|
String title;
|
||||||
String beginDateAndTime;
|
String beginDateAndTime;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Event(
|
||||||
|
val title: String,
|
||||||
|
val beginDateAndTime: String
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Instead of manually parsing the JSON line per line (which we could also do) we can have Moshi do the
|
Instead of manually parsing the JSON line per line (which we could also do) we can have Moshi do the
|
||||||
transformation automatically. We simply define another class `EventJson` that directly corresponds
|
transformation automatically. We simply define another class `EventJson` that directly corresponds
|
||||||
to the JSON structure:
|
to the JSON structure:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class EventJson {
|
class EventJson {
|
||||||
String title;
|
String title;
|
||||||
@@ -172,6 +301,19 @@ class EventJson {
|
|||||||
String begin_time;
|
String begin_time;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class EventJson(
|
||||||
|
val title: String,
|
||||||
|
val begin_date: String,
|
||||||
|
val begin_time: String
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
And another class with the appropriate `@FromJson` and `@ToJson` methods that are telling Moshi how
|
And another class with the appropriate `@FromJson` and `@ToJson` methods that are telling Moshi how
|
||||||
to convert an `EventJson` to an `Event` and back. Now, whenever we are asking Moshi to parse a JSON
|
to convert an `EventJson` to an `Event` and back. Now, whenever we are asking Moshi to parse a JSON
|
||||||
@@ -179,6 +321,9 @@ to an `Event` it will first parse it to an `EventJson` as an intermediate step.
|
|||||||
serialize an `Event` Moshi will first create an `EventJson` object and then serialize that object as
|
serialize an `Event` Moshi will first create an `EventJson` object and then serialize that object as
|
||||||
usual.
|
usual.
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class EventJsonAdapter {
|
class EventJsonAdapter {
|
||||||
@FromJson Event eventFromJson(EventJson eventJson) {
|
@FromJson Event eventFromJson(EventJson eventJson) {
|
||||||
@@ -197,21 +342,72 @@ class EventJsonAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class EventJsonAdapter {
|
||||||
|
@FromJson fun eventFromJson(eventJson: EventJson): Event {
|
||||||
|
val event = Event()
|
||||||
|
event.title = eventJson.title
|
||||||
|
event.beginDateAndTime = "${eventJson.begin_date} ${eventJson.begin_time}"
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
@ToJson fun eventToJson(event: Event): EventJson {
|
||||||
|
val json = EventJson()
|
||||||
|
json.title = event.title
|
||||||
|
json.begin_date = event.beginDateAndTime.substring(0, 8)
|
||||||
|
json.begin_time = event.beginDateAndTime.substring(9, 14)
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Again we register the adapter with Moshi.
|
Again we register the adapter with Moshi.
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Moshi moshi = new Moshi.Builder()
|
Moshi moshi = new Moshi.Builder()
|
||||||
.add(new EventJsonAdapter())
|
.add(new EventJsonAdapter())
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val moshi = Moshi.Builder()
|
||||||
|
.add(EventJsonAdapter())
|
||||||
|
.builder
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
We can now use Moshi to parse the JSON directly to an `Event`.
|
We can now use Moshi to parse the JSON directly to an `Event`.
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
JsonAdapter<Event> jsonAdapter = moshi.adapter(Event.class);
|
JsonAdapter<Event> jsonAdapter = moshi.adapter(Event.class);
|
||||||
Event event = jsonAdapter.fromJson(json);
|
Event event = jsonAdapter.fromJson(json);
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val jsonAdapter = moshi.adapter<Event>()
|
||||||
|
val event = jsonAdapter.fromJson(json)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Adapter convenience methods
|
### Adapter convenience methods
|
||||||
|
|
||||||
@@ -226,6 +422,9 @@ Moshi provides a number of convenience methods for `JsonAdapter` objects:
|
|||||||
These factory methods wrap an existing `JsonAdapter` into additional functionality.
|
These factory methods wrap an existing `JsonAdapter` into additional functionality.
|
||||||
For example, if you have an adapter that doesn't support nullable values, you can use `nullSafe()` to make it null safe:
|
For example, if you have an adapter that doesn't support nullable values, you can use `nullSafe()` to make it null safe:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String dateJson = "\"2018-11-26T11:04:19.342668Z\"";
|
String dateJson = "\"2018-11-26T11:04:19.342668Z\"";
|
||||||
String nullDateJson = "null";
|
String nullDateJson = "null";
|
||||||
@@ -242,6 +441,28 @@ Date nullDate = adapter.fromJson(nullDateJson);
|
|||||||
Date nullDate = adapter.nullSafe().fromJson(nullDateJson);
|
Date nullDate = adapter.nullSafe().fromJson(nullDateJson);
|
||||||
System.out.println(nullDate); // null
|
System.out.println(nullDate); // null
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val dateJson = "\"2018-11-26T11:04:19.342668Z\""
|
||||||
|
val nullDateJson = "null"
|
||||||
|
|
||||||
|
// Hypothetical IsoDateDapter, doesn't support null by default
|
||||||
|
val adapter: JsonAdapter<Date> = IsoDateDapter()
|
||||||
|
|
||||||
|
val date = adapter.fromJson(dateJson)
|
||||||
|
println(date) // Mon Nov 26 12:04:19 CET 2018
|
||||||
|
|
||||||
|
val nullDate = adapter.fromJson(nullDateJson)
|
||||||
|
// Exception, com.squareup.moshi.JsonDataException: Expected a string but was NULL at path $
|
||||||
|
|
||||||
|
val nullDate = adapter.nullSafe().fromJson(nullDateJson)
|
||||||
|
println(nullDate) // null
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
In contrast to `nullSafe()` there is `nonNull()` to make an adapter refuse null values. Refer to the Moshi JavaDoc for details on the various methods.
|
In contrast to `nullSafe()` there is `nonNull()` to make an adapter refuse null values. Refer to the Moshi JavaDoc for details on the various methods.
|
||||||
|
|
||||||
@@ -264,12 +485,27 @@ Say we have a JSON string of this structure:
|
|||||||
|
|
||||||
We can now use Moshi to parse the JSON string into a `List<Card>`.
|
We can now use Moshi to parse the JSON string into a `List<Card>`.
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
String cardsJsonResponse = ...;
|
String cardsJsonResponse = ...;
|
||||||
Type type = Types.newParameterizedType(List.class, Card.class);
|
Type type = Types.newParameterizedType(List.class, Card.class);
|
||||||
JsonAdapter<List<Card>> adapter = moshi.adapter(type);
|
JsonAdapter<List<Card>> adapter = moshi.adapter(type);
|
||||||
List<Card> cards = adapter.fromJson(cardsJsonResponse);
|
List<Card> cards = adapter.fromJson(cardsJsonResponse);
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val cardsJsonResponse: String = ...
|
||||||
|
// We can just use a reified extension!
|
||||||
|
val adapter = moshi.adapter<List<Card>>()
|
||||||
|
val cards: List<Card> = adapter.fromJson(cardsJsonResponse)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
### Fails Gracefully
|
### Fails Gracefully
|
||||||
|
|
||||||
@@ -317,11 +553,11 @@ But the two libraries have a few important differences:
|
|||||||
|
|
||||||
### Custom field names with @Json
|
### Custom field names with @Json
|
||||||
|
|
||||||
Moshi works best when your JSON objects and Java objects have the same structure. But when they
|
Moshi works best when your JSON objects and Java or Kotlin classes have the same structure. But when they
|
||||||
don't, Moshi has annotations to customize data binding.
|
don't, Moshi has annotations to customize data binding.
|
||||||
|
|
||||||
Use `@Json` to specify how Java fields map to JSON names. This is necessary when the JSON name
|
Use `@Json` to specify how Java fields or Kotlin properties map to JSON names. This is necessary when the JSON name
|
||||||
contains spaces or other characters that aren’t permitted in Java field names. For example, this
|
contains spaces or other characters that aren’t permitted in Java field or Kotlin property names. For example, this
|
||||||
JSON has a field name containing a space:
|
JSON has a field name containing a space:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -331,7 +567,10 @@ JSON has a field name containing a space:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
With `@Json` its corresponding Java class is easy:
|
With `@Json` its corresponding Java or Kotlin class is easy:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class Player {
|
class Player {
|
||||||
@@ -341,9 +580,23 @@ class Player {
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Because JSON field names are always defined with their Java fields, Moshi makes it easy to find
|
<details>
|
||||||
fields when navigating between Java and JSON.
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Player {
|
||||||
|
val username: String
|
||||||
|
@Json(name = "lucky number") val luckyNumber: Int
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Because JSON field names are always defined with their Java or Kotlin fields, Moshi makes it easy to find
|
||||||
|
fields when navigating between Java or Koltin and JSON.
|
||||||
|
|
||||||
### Alternate type adapters with @JsonQualifier
|
### Alternate type adapters with @JsonQualifier
|
||||||
|
|
||||||
@@ -363,6 +616,9 @@ Here’s a JSON message with two integers and a color:
|
|||||||
|
|
||||||
By convention, Android programs also use `int` for colors:
|
By convention, Android programs also use `int` for colors:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class Rectangle {
|
class Rectangle {
|
||||||
int width;
|
int width;
|
||||||
@@ -370,8 +626,21 @@ class Rectangle {
|
|||||||
int color;
|
int color;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
But if we encoded the above Java class as JSON, the color isn't encoded properly!
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Rectangle(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
|
val color: Int
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
But if we encoded the above Java or Kotlin class as JSON, the color isn't encoded properly!
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -383,15 +652,33 @@ But if we encoded the above Java class as JSON, the color isn't encoded properly
|
|||||||
|
|
||||||
The fix is to define a qualifier annotation, itself annotated `@JsonQualifier`:
|
The fix is to define a qualifier annotation, itself annotated `@JsonQualifier`:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@JsonQualifier
|
@JsonQualifier
|
||||||
public @interface HexColor {
|
public @interface HexColor {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@JsonQualifier
|
||||||
|
annotation class HexColor
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
Next apply this `@HexColor` annotation to the appropriate field:
|
Next apply this `@HexColor` annotation to the appropriate field:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class Rectangle {
|
class Rectangle {
|
||||||
int width;
|
int width;
|
||||||
@@ -399,9 +686,25 @@ class Rectangle {
|
|||||||
@HexColor int color;
|
@HexColor int color;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Rectangle(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
|
@HexColor val color: Int
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
And finally define a type adapter to handle it:
|
And finally define a type adapter to handle it:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
/** Converts strings like #ff0000 to the corresponding color ints. */
|
/** Converts strings like #ff0000 to the corresponding color ints. */
|
||||||
class ColorAdapter {
|
class ColorAdapter {
|
||||||
@@ -414,6 +717,24 @@ class ColorAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
/** Converts strings like #ff0000 to the corresponding color ints. */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Use `@JsonQualifier` when you need different JSON encodings for the same type. Most programs
|
Use `@JsonQualifier` when you need different JSON encodings for the same type. Most programs
|
||||||
shouldn’t need this `@JsonQualifier`, but it’s very handy for those that do.
|
shouldn’t need this `@JsonQualifier`, but it’s very handy for those that do.
|
||||||
@@ -423,6 +744,9 @@ shouldn’t need this `@JsonQualifier`, but it’s very handy for those that do.
|
|||||||
Some models declare fields that shouldn’t be included in JSON. For example, suppose our blackjack
|
Some models declare fields that shouldn’t be included in JSON. For example, suppose our blackjack
|
||||||
hand has a `total` field with the sum of the cards:
|
hand has a `total` field with the sum of the cards:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final class BlackjackHand {
|
public final class BlackjackHand {
|
||||||
private int total;
|
private int total;
|
||||||
@@ -430,9 +754,25 @@ public final class BlackjackHand {
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class BlackjackHand(
|
||||||
|
private val total: Int,
|
||||||
|
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
By default, all fields are emitted when encoding JSON, and all fields are accepted when decoding
|
By default, all fields are emitted when encoding JSON, and all fields are accepted when decoding
|
||||||
JSON. Prevent a field from being included by adding Java’s `transient` keyword:
|
JSON. Prevent a field from being included by adding Java’s `transient` keyword or Kotlin's `@Transient` annotation:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final class BlackjackHand {
|
public final class BlackjackHand {
|
||||||
@@ -441,6 +781,19 @@ public final class BlackjackHand {
|
|||||||
...
|
...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class BlackjackHand(...) {
|
||||||
|
@Transient var total: Int
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the
|
Transient fields are omitted when writing JSON. When reading JSON, the field is skipped even if the
|
||||||
JSON contains a value for the field. Instead, it will get a default value.
|
JSON contains a value for the field. Instead, it will get a default value.
|
||||||
@@ -448,13 +801,15 @@ JSON contains a value for the field. Instead, it will get a default value.
|
|||||||
|
|
||||||
### Default Values & Constructors
|
### Default Values & Constructors
|
||||||
|
|
||||||
When reading JSON that is missing a field, Moshi relies on the Java or Android runtime to assign
|
When reading JSON that is missing a field, Moshi relies on the Java or Kotlin or Android runtime to assign
|
||||||
the field’s value. Which value it uses depends on whether the class has a no-arguments constructor.
|
the field’s value. Which value it uses depends on whether the class has a no-arguments constructor.
|
||||||
|
|
||||||
If the class has a no-arguments constructor, Moshi will call that constructor and whatever value
|
If the class has a no-arguments constructor, Moshi will call that constructor and whatever value
|
||||||
it assigns will be used. For example, because this class has a no-arguments constructor the `total`
|
it assigns will be used. For example, because this class has a no-arguments constructor the `total`
|
||||||
field is initialized to `-1`.
|
field is initialized to `-1`.
|
||||||
|
|
||||||
|
Note: This section only applies to Java reflections.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final class BlackjackHand {
|
public final class BlackjackHand {
|
||||||
private int total = -1;
|
private int total = -1;
|
||||||
@@ -474,6 +829,7 @@ If the class doesn’t have a no-arguments constructor, Moshi can’t assign the
|
|||||||
numbers, `false` for booleans, and `null` for references. In this example, the default value of
|
numbers, `false` for booleans, and `null` for references. In this example, the default value of
|
||||||
`total` is `0`!
|
`total` is `0`!
|
||||||
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final class BlackjackHand {
|
public final class BlackjackHand {
|
||||||
private int total = -1;
|
private int total = -1;
|
||||||
@@ -489,6 +845,7 @@ This is surprising and is a potential source of bugs! For this reason consider d
|
|||||||
no-arguments constructor in classes that you use with Moshi, using `@SuppressWarnings("unused")` to
|
no-arguments constructor in classes that you use with Moshi, using `@SuppressWarnings("unused")` to
|
||||||
prevent it from being inadvertently deleted later:
|
prevent it from being inadvertently deleted later:
|
||||||
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public final class BlackjackHand {
|
public final class BlackjackHand {
|
||||||
private int total = -1;
|
private int total = -1;
|
||||||
@@ -511,6 +868,9 @@ adapters to build upon the standard conversion.
|
|||||||
|
|
||||||
In this example, we turn serialize nulls, then delegate to the built-in adapter:
|
In this example, we turn serialize nulls, then delegate to the built-in adapter:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
class TournamentWithNullsAdapter {
|
class TournamentWithNullsAdapter {
|
||||||
@ToJson void toJson(JsonWriter writer, Tournament tournament,
|
@ToJson void toJson(JsonWriter writer, Tournament tournament,
|
||||||
@@ -525,6 +885,27 @@ class TournamentWithNullsAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class TournamentWithNullsAdapter {
|
||||||
|
@ToJson fun toJson(writer: JsonWriter, tournament: Tournament?,
|
||||||
|
delegate: JsonAdapter<Tournament?>) {
|
||||||
|
val wasSerializeNulls: Boolean = writer.getSerializeNulls()
|
||||||
|
writer.setSerializeNulls(true)
|
||||||
|
try {
|
||||||
|
delegate.toJson(writer, tournament)
|
||||||
|
} finally {
|
||||||
|
writer.setLenient(wasSerializeNulls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
When we use this to serialize a tournament, nulls are written! But nulls elsewhere in our JSON
|
When we use this to serialize a tournament, nulls are written! But nulls elsewhere in our JSON
|
||||||
document are skipped as usual.
|
document are skipped as usual.
|
||||||
@@ -534,11 +915,28 @@ the encoding and decoding process for any type, even without knowing about the t
|
|||||||
this example, we customize types annotated `@AlwaysSerializeNulls`, which an annotation we create,
|
this example, we customize types annotated `@AlwaysSerializeNulls`, which an annotation we create,
|
||||||
not built-in to Moshi:
|
not built-in to Moshi:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@Target(TYPE)
|
@Target(TYPE)
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface AlwaysSerializeNulls {}
|
public @interface AlwaysSerializeNulls {}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Target(TYPE)
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
annotation class AlwaysSerializeNulls
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@AlwaysSerializeNulls
|
@AlwaysSerializeNulls
|
||||||
@@ -548,11 +946,28 @@ static class Car {
|
|||||||
String color;
|
String color;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@AlwaysSerializeNulls
|
||||||
|
class Car(
|
||||||
|
val make: String?,
|
||||||
|
val model: String?,
|
||||||
|
val color: String?
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
Each `JsonAdapter.Factory` interface is invoked by `Moshi` when it needs to build an adapter for a
|
Each `JsonAdapter.Factory` interface is invoked by `Moshi` when it needs to build an adapter for a
|
||||||
user's type. The factory either returns an adapter to use, or null if it doesn't apply to the
|
user's type. The factory either returns an adapter to use, or null if it doesn't apply to the
|
||||||
requested type. In our case we match all classes that have our annotation.
|
requested type. In our case we match all classes that have our annotation.
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary>Java</summary>
|
||||||
|
|
||||||
```java
|
```java
|
||||||
static class AlwaysSerializeNullsFactory implements JsonAdapter.Factory {
|
static class AlwaysSerializeNullsFactory implements JsonAdapter.Factory {
|
||||||
@Override public JsonAdapter<?> create(
|
@Override public JsonAdapter<?> create(
|
||||||
@@ -567,6 +982,24 @@ static class AlwaysSerializeNullsFactory implements JsonAdapter.Factory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Kotlin</summary>
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class AlwaysSerializeNullsFactory : JsonAdapter.Factory {
|
||||||
|
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||||
|
val rawType: Class<*> = type.rawType
|
||||||
|
if (!rawType.isAnnotationPresent(AlwaysSerializeNulls::class.java)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val delegate: JsonAdapter<Any> = moshi.nextAdapter(this, type, annotations)
|
||||||
|
return delegate.serializeNulls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
After determining that it applies, the factory looks up Moshi's built-in adapter by calling
|
After determining that it applies, the factory looks up Moshi's built-in adapter by calling
|
||||||
`Moshi.nextAdapter()`. This is key to the composition mechanism: adapters delegate to each other!
|
`Moshi.nextAdapter()`. This is key to the composition mechanism: adapters delegate to each other!
|
||||||
@@ -643,8 +1076,8 @@ encode as JSON:
|
|||||||
```kotlin
|
```kotlin
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class BlackjackHand(
|
data class BlackjackHand(
|
||||||
val hidden_card: Card,
|
val hidden_card: Card,
|
||||||
val visible_cards: List<Card>
|
val visible_cards: List<Card>
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user