Gson 解析 Json瞎暑,容錯(cuò)才是關(guān)鍵彤敛,舉幾個(gè)常用的實(shí)例与帆!

一. 序

前幾天寫(xiě)了一篇,關(guān)于利用 GSON 在 JSON 序列化和反序列化之間墨榄,數(shù)據(jù)容錯(cuò)的文章玄糟。最簡(jiǎn)單的利用 @SerializedName 注解來(lái)配置多個(gè)不同 JSON Key 值,或者再使用 @Expose 來(lái)配置一些例外的情況袄秩。更復(fù)雜一些的數(shù)據(jù)阵翎,可以使用 TypeAdapter 來(lái)解決,TypeAdapter 可以說(shuō)是一顆 GSON 解析 JSON 的銀彈之剧,所有復(fù)雜數(shù)據(jù)解析以及容錯(cuò)問(wèn)題郭卫,都可以通過(guò)它來(lái)解決。還不了解的可以先看看之前的文章《利用 Gson 做好 JSON 數(shù)據(jù)容錯(cuò)》背稼。

文章評(píng)論里和公眾號(hào)后臺(tái)有一些小伙伴贰军,針對(duì)具體數(shù)據(jù)容錯(cuò)的場(chǎng)景,提出了具體的問(wèn)題蟹肘。今天就在這篇文章里統(tǒng)一解答词疼,并且給出解決方案。

二. GSON 數(shù)據(jù)容錯(cuò)實(shí)例

就像前文中介紹的一樣帘腹,GSON 已經(jīng)提供了一些簡(jiǎn)單的注解贰盗,去做數(shù)據(jù)的容錯(cuò)處理。更復(fù)雜的操作竹椒,就需要用到 TypeAdapter 了童太,需要注意的是米辐,一旦上了 TypeAdapter 之后胸完,注解的配置就會(huì)失效。

2.1 什么是 TypeAdapter

TypeAdapter 是 GSON 2.1 版本開(kāi)始支持的一個(gè)抽象類翘贮,用于接管某些類型的序列化和反序列化赊窥。TypeAdapter 最重要的兩個(gè)方法就是 write()read() ,它們分別接管了序列化和反序列化的具體過(guò)程狸页。

如果想單獨(dú)接管序列化或反序列化的某一個(gè)過(guò)程锨能,可以使用 JsonSerializer 和 JsonDeserializer 這兩個(gè)接口,它們組合起來(lái)的效果和 TypeAdapter 類似芍耘,但是其內(nèi)部實(shí)現(xiàn)是不同的址遇。

簡(jiǎn)單來(lái)說(shuō),TypeAdapter 是支持流的斋竞,所以它比較省內(nèi)存倔约,但是使用起來(lái)有些不方便。而 JsonSerializer 和 JsonDeserializer 是將數(shù)據(jù)都讀到內(nèi)存中再進(jìn)行操作坝初,會(huì)比 TypeAdapter 更費(fèi)內(nèi)存浸剩,但是 API 使用起來(lái)更清晰一些钾军。

雖然 TypeAdapter 更省內(nèi)存,但是通常我們業(yè)務(wù)接口所使用的那點(diǎn)數(shù)據(jù)量绢要,所占用的內(nèi)存其實(shí)影響不大吏恭,可以忽略不計(jì)。

因?yàn)?TypeAdapter重罪、JsonSerializer 以及 JsonDeserializer 都需要配合 GsonBuilder.registerTypeAdapter() 方法樱哼,所以在本文中,此種接管方式剿配,統(tǒng)稱為 TypeAdapter 接管唇礁。

2.2 空字符串轉(zhuǎn) 0

對(duì)于一些強(qiáng)轉(zhuǎn)有效的類型轉(zhuǎn)換,GSON 本身是有一些默認(rèn)的容錯(cuò)機(jī)制的惨篱。比如:將字符串 “18” 轉(zhuǎn)換成 Java 中整型的 18盏筐,這是被默認(rèn)支持的。

例如我有一個(gè)記錄用戶信息的 User 類砸讳。

class User{
    var name = ""
    var age = 0
    override fun toString(): String {
        return """
            {
                "name":"${name}",
                "age":${age}
            }
        """.trimIndent()
    }
}

User 類中包含 nameage 兩個(gè)字段琢融,其中 age 對(duì)應(yīng)的 JSON 類型,可以是 18 也可以是 "18"簿寂,這都是允許的漾抬。

{
    "name":"承香墨影",
    "age":18 // "age":"18"
}

那假如服務(wù)端說(shuō),這個(gè)用戶沒(méi)有填年齡的信息常遂,所以直接返回了一個(gè)空串 ""纳令,那這個(gè)時(shí)候客戶端用 Gson 解析就悲劇了。

這當(dāng)然是服務(wù)端的問(wèn)題克胳,如果數(shù)據(jù)明確為 Int 類型平绩,那么就算是默認(rèn)值也應(yīng)該是 0 或者 -1。

但遇到這樣的情況漠另,你還用默認(rèn)的 GSON 策略去解析捏雌,你將得到一個(gè) Crash。

Caused by: com.google.gson.JsonSyntaxException: 
- java.lang.NumberFormatException: 
--  empty String

沒(méi)有一點(diǎn)意外也沒(méi)有一點(diǎn)驚喜的 Crash 了笆搓,那接下來(lái)看看如何解決這樣的數(shù)據(jù)容錯(cuò)問(wèn)題性湿?

因?yàn)檫@里的場(chǎng)景中,只需要反序列化的操作满败,所以我們實(shí)現(xiàn) JsonDeserializer 接口即可肤频,接管的是 Int 類型。直接上例子吧算墨。

class IntDefaut0Adapter : JsonDeserializer<Int> {
    override fun deserialize(json: JsonElement?, 
                             typeOfT: Type?, 
                             context: JsonDeserializationContext?): Int {
        if (json?.getAsString().equals("")) {
            return 0
        }
        try {
            return json!!.getAsInt()
        } catch (e: NumberFormatException) {
            return 0
        }
    }
}

fun intDefault0(){
    val jsonStr = """
        {
            "name":"承香墨影",
            "age":""
        }
    """.trimIndent()
    val user = GsonBuilder()
            .registerTypeAdapter(
                    Int::class.java,
                    IntDefaut0Adapter())
            .create()
            .fromJson<User>(jsonStr,User::class.java)
    Log.i("cxmydev","user: ${user.toString()}")
}

在 IntDefaut0Adapter 中宵荒,首先判斷數(shù)據(jù)字符串是否為空字符串 "",如果是則直接返回 0,否則將其按 Int 類型解析骇扇。在這個(gè)例子中摔竿,將整型 0 作為一個(gè)異常參數(shù)進(jìn)行處理。

2.3 null少孝、[]继低、List 轉(zhuǎn) List

還有一些小伙伴比較關(guān)心的,對(duì)于 JSONObject 和 JSONArray 兼容的問(wèn)題稍走。

例如需要返回一個(gè) List袁翁,翻譯成 JSON 數(shù)據(jù)就應(yīng)該是方括號(hào) [] 包裹的 JSONArray。但是在列表為空的時(shí)候婿脸,服務(wù)端返回的數(shù)據(jù)粱胜,什么情況都有可能。

{
    "name":"承香墨影",
    "languages":["EN","CN"] // 理想的數(shù)據(jù)
    // "languages":""
    // "languages":null
    // "languages":{}
}

例子的 JSON 中狐树,languages 字段表示當(dāng)前用戶所掌握的語(yǔ)言焙压。當(dāng)語(yǔ)言字段沒(méi)有被設(shè)置的時(shí)候,服務(wù)端返回的數(shù)據(jù)不一致抑钟,如何兼容呢涯曲?

我們?cè)谠镜?User 類中,增加一個(gè) languages 的字段在塔,類型為 ArrayList<String>幻件。

var languages = ArrayList<String>()

在 Java 中,列表集合都會(huì)實(shí)現(xiàn) List 接口蛔溃,所以我們?cè)趯?shí)現(xiàn) JsonDeserializer 的時(shí)候绰沥,解析攔截的應(yīng)該是 List。

在這個(gè)情況下贺待,可以使用 JsonElement 的 isJsonArray() 方法徽曲,判斷當(dāng)前是否是一個(gè)合法的 JSONArray 的數(shù)組,一旦不正確狠持,就直接返回一個(gè)空的集合即可疟位。

class ArraySecurityAdapter:JsonDeserializer<List<*>>{
    override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): List<*> {
      
        if(json.isJsonArray()){
            val newGson = Gson()
            return newGson.fromJson(json, typeOfT)
        }else{
            return Collections.EMPTY_LIST
        }
    }
}

fun listDefaultEmpty(){
    val jsonStr = """
        {
            "name":"承香墨影",
            "age":"18",
            "languages":{}
        }
    """.trimIndent()
    val user = GsonBuilder()
            .registerTypeHierarchyAdapter(
                    List::class.java,
                    ArraySecurityAdapter())
            .create()
            .fromJson<User>(jsonStr,User::class.java)
    Log.i("cxmydev","user: ${user.toString()}")
}

其核心就是 isJsonArray() 方法,判斷當(dāng)前是否是一個(gè) JSONArray喘垂,如果是,再具體解析即可绍撞。到這一步就很靈活了正勒,你可以直接用 Gson 將數(shù)據(jù)反序列化成一個(gè) List,也可以將通過(guò)一個(gè) for 循環(huán)將其中的每一項(xiàng)單獨(dú)反序列化傻铣。

需要注意的是章贞,如果依然想用 Gson 來(lái)解析,需要重新創(chuàng)建一個(gè)新的 Gson 對(duì)象非洲,不可以直接復(fù)用 JsonDeserializationContext鸭限,否則會(huì)造成遞歸調(diào)用蜕径。

另外還有一個(gè)細(xì)節(jié),在這個(gè)例子中败京,調(diào)用的是 registerTypeHierarchyAdapter() 方法來(lái)注冊(cè) TypeAdapter兜喻,它和我們前面介紹的 registerTypeAdapter() 有什么區(qū)別呢?

通常我們會(huì)根據(jù)不同的場(chǎng)景赡麦,選擇不同數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的集合類朴皆,例如 ArrayList 或者 LinkedList。但是 registerTypeAdapter() 方法泛粹,要求我們傳遞一個(gè)明確的類型遂铡,也就是說(shuō)它不支持繼承,而 registerTypeHierarchyAdapter() 則可以支持繼承晶姊。

我們想用 List 來(lái)替代所有的 List 子類扒接,就需要使用 registerTypeHierarchyAdapter() 方法,或者我們的 Java Bean 中们衙,只使用 List珠增。這兩種情況都是可以的。

2.4 保留原 Json 字符串

看到這個(gè)小標(biāo)題砍艾,可能會(huì)有疑問(wèn)蒂教,保留原 Json 字符串是一個(gè)什么情況?得到的 Json 數(shù)據(jù)脆荷,本身就是一個(gè)字符串凝垛,且挺我細(xì)細(xì)說(shuō)來(lái)。

舉個(gè)例子蜓谋,前面定義的 User 類梦皮,需要存到 SQLite 數(shù)據(jù)庫(kù)中,語(yǔ)言(languages)字段也是需要存儲(chǔ)的桃焕。說(shuō)到 SQLite剑肯,當(dāng)然優(yōu)先使用一些開(kāi)源的 ORM 框架了,而不少優(yōu)秀的 ORM-SQLite 框架观堂,都通過(guò)外鍵的形式支持了一對(duì)多的存儲(chǔ)让网。例如一篇文章對(duì)應(yīng)多條評(píng)論,一條用戶信息對(duì)應(yīng)對(duì)應(yīng)多條語(yǔ)言信息师痕。

這種場(chǎng)景下我們當(dāng)然可以使用 ORM 框架本身提供的一對(duì)多的存儲(chǔ)形式溃睹。但是如果像現(xiàn)在的例子中,只是簡(jiǎn)單的存儲(chǔ)一些有限的數(shù)據(jù)胰坟,例如用戶會(huì)的語(yǔ)言(languages)因篇,這種簡(jiǎn)單的有限數(shù)據(jù),用外鍵有一些偏重了。

此時(shí)我們就想竞滓,要是可以直接在 SQLite 中存儲(chǔ) languages 字段的 JSON咐吼,將其當(dāng)成一個(gè)字符串去存儲(chǔ),是不是就簡(jiǎn)單了商佑?把一個(gè)多級(jí)的結(jié)構(gòu)拉平成一級(jí)锯茄,剩下的只需要擴(kuò)展出一個(gè)反序列化的方法,對(duì)業(yè)務(wù)來(lái)說(shuō)莉御,這些操作都是透明的撇吞。

那拍腦袋想,如果 Gson 有簡(jiǎn)單的容錯(cuò)礁叔,那我們將這個(gè)解析的字段類型定義成 String牍颈,是不是就可以做到了?

@SerializedName("languages")
var languageStr = ""

很遺憾琅关,這并沒(méi)有辦法做到煮岁,如果你這樣使用,你將得到一個(gè) IllegalStateException 的異常涣易。

Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY at line 4 column 18 path $.languages

之所以會(huì)出現(xiàn)這樣的情況画机,簡(jiǎn)單來(lái)說(shuō),雖然 deserialize() 方法傳遞的參數(shù)都是 JsonElement新症,但是 JsonElement 只是一個(gè)抽象類步氏,最終會(huì)根據(jù)數(shù)據(jù)的情況,轉(zhuǎn)換成它的幾個(gè)實(shí)現(xiàn)類的其中之一徒爹,這些實(shí)現(xiàn)類都是 final class荚醒,分別是 JsonObject、JsonArray隆嗅、JsonPrimitive界阁、JsonNull,這些從命名上就很好理解了胖喳,它們代表了不通的 JSON 數(shù)據(jù)場(chǎng)景泡躯,就不過(guò)多介紹了。

使用了 Gson 之后丽焊,遇到花括號(hào) {} 會(huì)生成一個(gè) JsonObject较剃,而字符串則是基本類型的 JsonPrimitive 對(duì)象,它們?cè)?Gson 內(nèi)部的解析流程是不一樣的粹懒,這就造成了 IllegalStateException 異常重付。

那么接下來(lái)看看如何解決這個(gè)問(wèn)題。

既然 TypeAdapter 是 Gson 解析的銀彈凫乖,找不到解決方案,用它就對(duì)了。思路繼續(xù)是用 JsonDeserializer 來(lái)接管解析帽芽,這一次將 User 類的整個(gè)解析都接管了删掀。

class UserGsonAdapter:JsonDeserializer<User>{
    override fun deserialize(json: JsonElement, 
                             typeOfT: Type?, 
                             context: JsonDeserializationContext?): User {
        
        var user = User()
        if(json.isJsonObject){
            val jsonObject = JSONObject(json.asJsonObject.toString())
            user.name = jsonObject.optString("name")
            user.age = jsonObject.optInt("age")
            user.languageStr = jsonObject.optString("languages")
            user.languages = ArrayList()
            val languageJsonArray = JSONArray(user.languageStr)
            for(i in 0 until languageJsonArray.length()){
                user.languages.add(languageJsonArray.optString(i))
            }
        }
        return user
    }
}

fun userGsonStr(){
    val jsonStr = """
        {
            "name":"承香墨影",
            "age":"18",
            "languages":["CN","EN"]
        }
    """.trimIndent()
    val user = GsonBuilder()
            .registerTypeAdapter(
                    User::class.java,
                    UserGsonAdapter())
            .create()
            .fromJson<User>(jsonStr,User::class.java)
    Log.i("cxmydev","user: \n${user.toString()}")
}

在這里我直接使用標(biāo)準(zhǔn) API org.json 包中的類去解析 JSON 數(shù)據(jù),當(dāng)然你也可以通過(guò) Gson 本身提供的一些方法去解析导街,這里只是提供一個(gè)思路而已披泪。

最終 Log 輸出的效果如下:

{
    "name":"承香墨影",
    "age":18,
    "languagesJson":["CN","EN"],
    "languages size:"2
}

在這個(gè)例子中,最終解析還是使用了標(biāo)準(zhǔn)的 JSONObject 和 JSONArray 類搬瑰,和 Gson 沒(méi)有任何關(guān)系款票,Gson 只是起到了一個(gè)橋接的作用,好像這個(gè)例子也沒(méi)什么實(shí)際用處泽论。

不談場(chǎng)景說(shuō)應(yīng)用都是耍流氓艾少,那么如果是使用 Retrofit 呢?Retrofit 可以配置 Gson 做為數(shù)據(jù)的轉(zhuǎn)換器翼悴,在其內(nèi)部就完成了反序列化的過(guò)程缚够。這種情況,配合 Gson 的 TypeAdapter鹦赎,就不需要我們?cè)陬~外的編寫(xiě)解析的代碼了谍椅,網(wǎng)絡(luò)請(qǐng)求走一套邏輯即可。

如果覺(jué)得在構(gòu)造 Retrofit 的時(shí)候古话,為 Gson 添加 TypeAdapter 有些入侵嚴(yán)重了雏吭,可以配合 @JsonAdapter 注解使用。

三. 小結(jié)時(shí)刻

針對(duì)服務(wù)端返回?cái)?shù)據(jù)的容錯(cuò)處理陪踩,很大一部分其實(shí)都是來(lái)自雙端沒(méi)有保證數(shù)據(jù)一致的問(wèn)題杖们。而針對(duì)開(kāi)發(fā)者來(lái)說(shuō),要做到外部數(shù)據(jù)均不可信的膊毁,客戶端不信本地讀取的數(shù)據(jù)胀莹、不信服務(wù)端返回的數(shù)據(jù),服務(wù)端也不能相信客戶端傳遞的數(shù)據(jù)婚温。這就是所謂防御式編程描焰。

言歸正傳,我們小結(jié)一下本文的內(nèi)容:

  1. TypeAdapter(包含JsonSerializer栅螟、JsonDeserializer) 是 Gson 解析的銀彈荆秦,所有 Json 解析的定制化要求都可以通過(guò)它來(lái)實(shí)現(xiàn)。
  2. registerTypeAdapter() 方法需要制定確定的數(shù)據(jù)類型力图,如果想支持繼承步绸,需要使用 registerTypeHierarchyAdapter() 方法。
  3. 如果數(shù)據(jù)量不大吃媒,推薦使用 JsonSerializer 和 JsonDeserializer瓤介。
  4. 針對(duì)整個(gè) Java Bean 的解析接管吕喘,可以使用 @JsonAdapter 注解。

就這樣吧刑桑,有問(wèn)題在推文文末留言氯质。

本文對(duì)你有幫助嗎?留言祠斧、點(diǎn)贊闻察、轉(zhuǎn)發(fā)是最大的支持,謝謝琢锋!


公眾號(hào)后臺(tái)回復(fù)成長(zhǎng)『成長(zhǎng)』辕漂,將會(huì)得到我準(zhǔn)備的學(xué)習(xí)資料,也能回復(fù)『加群』吴超,一起學(xué)習(xí)進(jìn)步钉嘹;你還能回復(fù)『提問(wèn)』,向我發(fā)起提問(wèn)烛芬。

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末隧期,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子赘娄,更是在濱河造成了極大的恐慌仆潮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遣臼,死亡現(xiàn)場(chǎng)離奇詭異性置,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)揍堰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)鹏浅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人屏歹,你說(shuō)我怎么就攤上這事隐砸。” “怎么了蝙眶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵季希,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我幽纷,道長(zhǎng)式塌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任友浸,我火速辦了婚禮峰尝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘收恢。我一直安慰自己武学,他們只是感情好祭往,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著劳淆,像睡著了一般链沼。 火紅的嫁衣襯著肌膚如雪默赂。 梳的紋絲不亂的頭發(fā)上沛鸵,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音缆八,去河邊找鬼曲掰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奈辰,可吹牛的內(nèi)容都是我干的栏妖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奖恰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吊趾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起瑟啃,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤论泛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蛹屿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體屁奏,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年错负,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坟瓢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡犹撒,死狀恐怖折联,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情识颊,我是刑警寧澤诚镰,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谊囚,受9級(jí)特大地震影響怕享,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镰踏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一函筋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奠伪,春花似錦跌帐、人聲如沸首懈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)究履。三九已至,卻和暖如春脸狸,著一層夾襖步出監(jiān)牢的瞬間最仑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工炊甲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泥彤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓卿啡,卻偏偏與公主長(zhǎng)得像吟吝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颈娜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353