Moshi
文章为对Moshi官方介绍的简单翻译。方便个人学习,加深印象。官方介绍:https://github.com/square/moshi
基础用法
依赖
Maven
<dependency>
<groupId>com.squareup.moshi</groupId>
<artifactId>moshi</artifactId>
<version>1.9.2</version>
</dependency>
Gradle
implementation("com.squareup.moshi:moshi:1.9.2")
json to object
String json = ...;
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
BlackjackHand blackjackHand = jsonAdapter.fromJson(json);
System.out.println(blackjackHand);
object to json
BlackjackHand blackjackHand = new BlackjackHand(
new Card('6', SPADES),
Arrays.asList(new Card('4', CLUBS), new Card('A', HEARTS)));
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
String json = jsonAdapter.toJson(blackjackHand);
System.out.println(json);
Java类型的支持
- 原始类型(int, float, char…) 和对应的封装类型 (Integer, Float, Character…).
- Arrays, Collections, Lists, Sets, and Maps
- Strings
- Enums
自定义类型适配器
class CardAdapter {
@ToJson String toJson(Card card) {
return card.rank + card.suit.name().substring(0, 1);
}
@FromJson Card fromJson(String card) {
if (card.length() != 2) throw new JsonDataException("Unknown card: " + card);
char rank = card.charAt(0);
switch (card.charAt(1)) {
case 'C': return new Card(rank, Suit.CLUBS);
case 'D': return new Card(rank, Suit.DIAMONDS);
case 'H': return new Card(rank, Suit.HEARTS);
case 'S': return new Card(rank, Suit.SPADES);
default: throw new JsonDataException("unknown suit: " + card);
}
}
}
Moshi moshi = new Moshi.Builder()
.add(new CardAdapter())
.build();
便利的适配器方法
Moshi为JsonAdapter对象提供了许多便利的方法:
nullSafe()
nonNull()
lenient()
failOnUnknown()
indent()
serializeNulls()
String dateJson = "\"2018-11-26T11:04:19.342668Z\"";
String nullDateJson = "null";
// Hypothetical IsoDateDapter, doesn't support null by default
JsonAdapter<Date> adapter = new IsoDateDapter();
Date date = adapter.fromJson(dateJson);
System.out.println(date); // Mon Nov 26 12:04:19 CET 2018
Date nullDate = adapter.fromJson(nullDateJson);
// Exception, com.squareup.moshi.JsonDataException: Expected a string but was NULL at path $
Date nullDate = adapter.nullSafe().fromJson(nullDateJson);
System.out.println(nullDate); // null
解析JSON数组
[
{
"rank": "4",
"suit": "CLUBS"
},
{
"rank": "A",
"suit": "HEARTS"
}
]
String cardsJsonResponse = ...;
Type type = Types.newParameterizedType(List.class, Card.class);
JsonAdapter<List<Card>> adapter = moshi.adapter(type);
List<Card> cards = adapter.fromJson(cardsJsonResponse);
解析异常处理
JsonDataException: Expected one of [CLUBS, DIAMONDS, HEARTS, SPADES] but was ANCHOR at path $.visible_cards[2].suit
at com.squareup.moshi.JsonAdapters$11.fromJson(JsonAdapters.java:188)
at com.squareup.moshi.JsonAdapters$11.fromJson(JsonAdapters.java:180)
...
- 如果读取JSON文档时出错或格式错误,则Moshi始终会抛出标准的java.io.IOException
- 如果JSON文档格式正确,但与预期格式不匹配,则会引发JsonDataException
使用@Json的自定义字段名称
{
"username": "jesse",
"lucky number": 32
}
class Player {
String username;
@Json(name = "lucky number") int luckyNumber;
...
}
备用类型适配器@JsonQualifier
若有如下JSON串
{
"width": 1024,
"height": 768,
"color": "#ff0000"
}
Android中定义对应的类
class Rectangle {
int width;
int height;
int color;
}
但是,如果我们将上述Java类解析成JSON,则颜色编码不正确!
{
"width": 1024,
"height": 768,
"color": 16711680
}
解决方法是定义一个限定符注解,其自身注解@JsonQualifier
:
@Retention(RUNTIME)
@JsonQualifier
public @interface HexColor {
}
接下来,将此@HexColor
注解到应用到对应的字段:
class Rectangle {
int width;
int height;
@HexColor int color;
}
最后定义一个类型适配器来处理它:
/** Converts strings like #ff0000 to the corresponding color ints. */
class ColorAdapter {
@ToJson String toJson(@HexColor int rgb) {
return String.format("#%06x", rgb);
}
@FromJson @HexColor int fromJson(String rgb) {
return Integer.parseInt(rgb.substring(1), 16);
}
}
忽略字段transient
public final class BlackjackHand {
private transient int total;
...
}
写JSON时会忽略transient
字段。 读JSON时,即使JSON包含该字段的值,也会跳过该字段。
默认值&& 构造函数
在读取少一个字段的JSON串时,Moshi依赖Java或Android运行时情况来给缺失字段赋默认值。 至于如何赋值取决于类是否具有无参数构造函数。
如果类具有无参数构造函数,则Moshi将调用该构造函数,并使用它分配的值来给字段赋值。 例如,由于此类具有无参数构造函数,因此将total
字段默认值为-1
public final class BlackjackHand {
private int total = -1;
...
private BlackjackHand() {
}
public BlackjackHand(Card hidden_card, List<Card> visible_cards) {
...
}
}
如果类没有无参数构造函数,则**即使对象内明确指定了字段的值,Moshi也无法给字段赋默认值。**这种情况下,字段的默认值始终为0(数字),false(布尔值)和null(引用)。 例如,由于此类没有无参数构造函数,因此将total
字段默认值为0
public final class BlackjackHand {
private int total = -1;
...
public BlackjackHand(Card hidden_card, List<Card> visible_cards) {
...
}
}
上面这个特点很容易被忽视,也有可能成为一些错误的根源。因此,我们建议在与Moshi一起使用的类中都定义一个无参数的构造函数,并使用@SuppressWarnings(“ unused”)
防止其在以后被无意删除。
public final class BlackjackHand {
private int total = -1;
...
@SuppressWarnings("unused") // Moshi uses this!
private BlackjackHand() {
}
public BlackjackHand(Card hidden_card, List<Card> visible_cards) {
...
}
}
Kotlin
Moshi配合Kotlin使用效果更佳。 它适配Kotlin的非空类型和默认参数值等特有属性。
Kotlin与Moshi结合使用时,我们可以使用使用reflection, codegen等辅助工具,方便我们快速完成JSON的处理。
Reflection
反射适配器使用Kotlin的反射库将Kotlin类与JSON相互转换。使用如下:
val moshi = Moshi.Builder()
// ... add your own JsonAdapters and factories ...
.add(KotlinJsonAdapterFactory())
.build()
Moshi的适配器按优先级排序,因此需要将Kotlin适配器放在自定义适配器之后,否则,KotlinJsonAdapterFactory
将具有优先权,自定义适配器将失效。
reflection适配器需要添加如下依赖才可使用
<dependency>
<groupId>com.squareup.moshi</groupId>
<artifactId>moshi-kotlin</artifactId>
<version>1.9.2</version>
</dependency>
implementation("com.squareup.moshi:moshi-kotlin:1.9.2")
Codegen
Moshi的Kotlin Codegen支持是一个注释处理器。 它在编译时为每个Kotlin类生成一个小型且快速的适配器。 使用如下:
@JsonClass(generateAdapter = true)
data class BlackjackHand(
val hidden_card: Card,
val visible_cards: List<Card>
)
Codegen适配器要求Kotlin类及其属性必须是internal
的或public
的(默认可见性)。
Codegen使用没有额外的依赖。只需使用kotlin kapt并按照如下添加自己的注解处理器。
<dependency>
<groupId>com.squareup.moshi</groupId>
<artifactId>moshi-kotlin-codegen</artifactId>
<version>1.9.2</version>
<scope>provided</scope>
</dependency>
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")
局限性
如果Kotlin类有父类,则其父类必须也是Kotlin类。 reflection或codegen生成均不支持Java父类型的Kotlin类或Kotlin父类型的Java类。 如果需要将此类转换为JSON,则必须创建一个自定义类型适配器。
无论使用reflection还是codegen,Kotlin类的JSON编码都是相同的。 使用codegen的优势在于它性能好同时也不需要依赖kotlin-reflect; 使用reflection的优势在于可转换私有和受保护的属性。 如果同时使用两者,则生成的适配器将用于带有@JsonClass(generateAdapter = true)
注释的类。
扫码关注,持续更新
回复【计算机视觉】获取计算机视觉相关必备学习资料
回复【Android】获取Android,Kotlin必备学习资料