一、概述
Android(Java) 平台已经有许多 Json 库了,包括 Google 推荐的 Gson,广受欢迎的 Jackson,阿里的 FastJson 等,但今天要说的是大名鼎鼎 Square 公司的 MoShi 。
轮子已经有很多了,不过自从 Google 将 Kotlin 定为 Android 亲儿子开发语言后,竟然包括 Gson 在内的几乎所有 Json 库均不支持,这当然可以理解,因为他们大多数采用了 Java 反射机制,注定难以适配 Kotlin 独有的特性。因此今天要介绍 Moshi —— 一个与 Kotlin 兼容极好的现代化解析库。可以很好的兼容 java 和 Kotlin。
二、java中使用
导入
implementation 'com.squareup.moshi:moshi:1.12.0'
Bean类
public class JavaBean {
private String name;
private String sex;
private int age;
public JavaBean(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "JavaBean{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
Bean
json -> bean
private static void json2Bean() {
String jj = "{\"age\":22,\"name\":\"alice\",\"sex\":\"man\"}";
JsonAdapter<JavaBean> adapter = new Moshi.Builder().build().adapter(JavaBean.class);
try {
JavaBean bean = adapter.fromJson(jj);
System.out.println(bean);
} catch (IOException e) {
e.printStackTrace();
}
}
bean -> json
private static void bean2Json() {
JavaBean javaBean = new JavaBean("alice", "man", 22);
JsonAdapter<JavaBean> adapter = new Moshi.Builder().build().adapter(JavaBean.class);
String json = adapter.toJson(javaBean);
System.out.println(json);
}
Map
map -> json
private static void map2Json() {
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
Moshi moshi = new Moshi.Builder().build();
ParameterizedType type = Types.newParameterizedType(Map.class, String.class, Integer.class);
JsonAdapter<Object> adapter = moshi.adapter(type);
String json = adapter.toJson(map);
System.out.println(json);
}
json -> map
private static void json2Map() {
String json = "{\"one\":1,\"two\":2,\"three\":3}";
Moshi moshi = new Moshi.Builder().build();
ParameterizedType type = Types.newParameterizedType(Map.class, String.class, Integer.class);
JsonAdapter<Map<String, Integer>> adapter = moshi.adapter(type);
try {
Map<String, Integer> map = adapter.fromJson(json);
System.out.println(map);
} catch (IOException e) {
e.printStackTrace();
}
}
List
list -> json
private static void list2Json() {
ArrayList<JavaBean> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
list.add(new JavaBean("alice" + i, "man" + i, 23 + i));
}
Type listOfCardsType = Types.newParameterizedType(List.class, JavaBean.class);
JsonAdapter<Object> adapter = new Moshi.Builder().build().adapter(listOfCardsType);
String json = adapter.toJson(list);
System.out.println(json);
}
json -> list
private static void json2list() {
String json = "[{\"age\":23,\"name\":\"alice0\",\"sex\":\"man0\"},{\"age\":24,\"name\":\"alice1\",\"sex\":\"man1\"},{\"age\":25,\"name\":\"alice2\",\"sex\":\"man2\"}]";
Type listOfCardsType = Types.newParameterizedType(List.class, JavaBean.class);
JsonAdapter<List<JavaBean>> adapter = new Moshi.Builder().build().adapter(listOfCardsType);
try {
List<JavaBean> list = adapter.fromJson(json);
System.out.println(list);
} catch (IOException e) {
e.printStackTrace();
}
}
Others
@json:Key转换
- transitent:跳过该字段不解析
public final class Bean {
@Json(name = "lucky number") int luckyNumber;
@Json(name = "objec") int data;
@Json(name = "toatl_price") String totolPrice;
private transient int total;//jump the field
}
三、Kotlin中使用
这里有两种,第一种是用反射的方式,不过这种方式有两个缺点,一是引入的包有点大,两一个就是对性能有影响。所以这里用注解的方式。
引入kapt:
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.12.0'
Bean类
@JsonClass(generateAdapter = true)
data class KotlinBean(var name: String, var age: Int)
或者下面这种:
@JsonClass(generateAdapter = true)
data class TestCode(
@Json(name = "age")
var age: Int,
@Json(name = "name")
var name: String
){
fun isDTwty() = age > 20
}
Bean
@OptIn(ExperimentalStdlibApi::class)
fun json2Bean() {
val jsont = "{\"name\":\"alice\",\"age\":33}"
val adapter = Moshi.Builder().build().adapter<KotlinBean>()
println(adapter.fromJson(jsont))
}
@SuppressLint("CheckResult")
@OptIn(ExperimentalStdlibApi::class)
private fun bean2Json() {
val adapter = Moshi.Builder().build().adapter<KotlinBean>()
println(adapter.toJson(KotlinBean("alice", 33)))
}
Map
fun json2Map() {
val json = "{\"name\":\"hello\",\"age\":99}"
val types = Types.newParameterizedType(Map::class.java, String::class.java,Any::class.java)
val adapter = Moshi.Builder().build().adapter<Map<String,Any>>(types)
val map = adapter.fromJson(json)
println(map?.get("name"))
}
fun map2Json() {
val map = mapOf("name" to "hello","age" to 99)
val types = Types.newParameterizedType(Map::class.java, String::class.java,Any::class.java)
val adapter = Moshi.Builder().build().adapter<Map<String,Any>>(types)
val json = adapter.toJson(map)
println(json)
}
List
fun json2List() {
println("-------------")
val json = "[{\"name\":\"alice\",\"age\":22},{\"name\":\"bob\",\"age\":12}]"
val type = Types.newParameterizedType(List::class.java,KotlinBean::class.java)
val adapter = Moshi.Builder().build().adapter<List<KotlinBean>>(type)
val list = adapter.fromJson(json)
list?.forEach { println(it) }
}
fun list2Json() {
val list = listOf(KotlinBean("alice",22),KotlinBean("bob",12))
val type = Types.newParameterizedType(List::class.java,KotlinBean::class.java)
val adapter = Moshi.Builder().build().adapter<List<KotlinBean>>(type)
val json = adapter.toJson(list)
println(json)
}
Custom Type Adapters
@JsonClass(generateAdapter = true)
data class PersonAd(var name: String?, var age: Int,var sexAd: SexAd)
enum class SexAd{
MAN,WOMAN;
}
class SexAdapter{
@FromJson
fun fromJson(value: Int): SexAd{
return if (value == 0) SexAd.MAN else SexAd.WOMAN
}
@ToJson
fun toJson(sexAd: SexAd): Int{
return if (sexAd == SexAd.MAN) 0 else 1
}
}
class CommonAdapter{
@FromJson
fun fromJson(value: String?): String{
//如果json数据没有这个字段,不执行这里
println("fromJson--$value")
return if (value.isNullOrEmpty()) "这是空" else value
}
@ToJson
fun toJson(value: String?): String{
return if (value.isNullOrEmpty()) "this is null" else value
}
}
@ExperimentalStdlibApi
fun main() {
//测试SexAdapter
/* val personAd = PersonAd("alice",99,SexAd.MAN)
val moshi = Moshi.Builder().add(SexAdapter()).build()
val adapter = moshi.adapter<PersonAd>()
val json = adapter.toJson(personAd)
println(json)
val jj = "{\"name\":\"alice\",\"age\":99,\"sexAd\":1}"
println("------${GsonKtx.toJson(personAd)}")
val fromJson = adapter.fromJson(jj)
println(fromJson)*/
println("---------------")
//测试通用CommonAdapter
val personAd = PersonAd("",99,SexAd.MAN)
val moshi = Moshi.Builder()
.add(CommonAdapter())
// .add(SexAdapter())
.build()
val adapter = moshi.adapter<PersonAd>()
val json = adapter.toJson(personAd)
println(json)
val jj = "{\"name\":null,\"age\":99,\"sexAd\":\"MAN\"}"
val person = adapter.fromJson(jj)
println(person)
}
Codegen
使用codegen会自动生成adpter,可以直接使用。导入kapt依赖,在bean类上加上@JsonClass(generateAdapter = true),示例如下:
@JsonClass(generateAdapter = true)
data class TestCode(
@Json(name = "age")
var age: Int,
@Json(name = "name")
var name: String
){
fun isDTwty() = age > 20
}
序列化和反序列化:
val bean = TestCode(9,"moshi")
println(TestCodeJsonAdapter(Moshi.Builder().build()).fromJson("{\"age\":9,\"name\":\"moshi\"}"))
println(TestCodeJsonAdapter(Moshi.Builder().build()).toJson(bean))
四、封装代码
object MSKtx {
val moshi: Moshi = Moshi.Builder().build()
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> toJson(t: T?): String? {
if (t == null) return null
return moshi.adapter<T>().toJson(t)
}
fun <T> fromJson(json: String?, type: Type): T? {
if (json.isNullOrEmpty()) {
return null
}
val adapter = moshi.adapter<T>(type)
return adapter.fromJson(json)
}
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> toAny(json: String?): T? {
if (json.isNullOrEmpty()) {
return null
}
val adapter = moshi.adapter<T>()
return adapter.fromJson(json)
}
inline fun <reified K, reified V> toMap(json: String?): MutableMap<K, V>? =
fromJson(json, Types.newParameterizedType(Map::class.java, K::class.java, V::class.java))
inline fun <reified T> toList(json: String?): MutableList<T>? =
fromJson(json, Types.newParameterizedType(MutableList::class.java, T::class.java))
}
fun Any?.toJson() = MSKtx.toJson(this)
fun <T> String?.fromJson(type: Type) = MSKtx.fromJson<T>(this, type)
inline fun <reified T> String?.toAny() = MSKtx.toAny<T>(this)
inline fun <reified K, reified V> String?.toMap() = MSKtx.toMap<K, V>(this)
inline fun <reified T> String?.toList() = MSKtx.toList<T>(this)
java
private static void skTest() {
JavaBean javaBean = new JavaBean("alice", "man", 22);
System.out.println(MSKtxKt.toJson(javaBean));
String jj = "{\"age\":22,\"name\":\"alice\",\"sex\":\"man\"}";
JavaBean bean = MSKtxKt.fromJson(jj, Types.newParameterizedType(JavaBean.class));
System.out.println(bean);
}
Kotlin
println(MSKtx.toAny<TestCode>("{\"age\":9,\"name\":\"moshi\"}"))
/*println(MSKtx.toJson(KotlinBean("alice", 33)))
println(MSKtx.toJson(mapOf("name" to "hello","age" to 99)))
println(MSKtx.toJson(listOf(KotlinBean("alice",22),KotlinBean("bob",12))))
println("-------------")
println(MSKtx.toAny<KotlinBean>("{\"name\":\"alice\",\"age\":33}"))
println(MSKtx.toMap<String, Any>("{\"name\":\"hello\",\"age\":99}"))
println(MSKtx.toList<KotlinBean>("[{\"name\":\"alice\",\"age\":22},{\"name\":\"bob\",\"age\":12}]"))*/