Moshi

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必备学习资料

onlyloveyd CSDN认证博客专家 Android Kotlin OpenCV
个人公众号【Android or OpenCV】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注技术与研发。
©️2020 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值