442,Swift Codable使用及說(shuō)明(面試點(diǎn):public typealias Codable = Decodable & Encodable,可以用JSONDecoder解碼和JSON...

前言

本篇是Swift4中Codable的使用系列第一篇,通過(guò)本篇文章我們來(lái)了解Codable的基本用法决侈。

自Swift4發(fā)布以來(lái)已有一段時(shí)間了,各種新特性為我們提供更加高效的開(kāi)發(fā)效率,其中在Swift4中使用Codable協(xié)議進(jìn)行模型與json數(shù)據(jù)之間的映射提供更加便利的方式干像。在Swift3中,對(duì)于從服務(wù)器獲取到的json數(shù)據(jù)后,我們要進(jìn)行一系列繁瑣的操作才能將數(shù)據(jù)完整的轉(zhuǎn)化成模型麻汰,舉個(gè)??速客,我們從服務(wù)器獲取了一段這樣的json數(shù)據(jù):

{
    "student": {
        "name": "Jone",
        "age": 18,
        "finger": {
            "count": 10
        }
    }
}

然后我們用JSONSerialization來(lái)解析數(shù)據(jù),得到的是一個(gè)Any類型五鲫。當(dāng)我們要讀取count時(shí)就要采取以下操作:

let json = try! JSONSerialization.jsonObject(with: data, options: [])
if let jsonDic = json as? [String:Any] {
    if let student = jsonDic["student"] as? [String:Any] {
        if let finger = student["finger"] as? [String:Int] {
            if let count = finger["count"] {
                print(count)
            }
        }
    }
}

難過(guò)不難過(guò)...在日常用Swift編寫(xiě)代碼時(shí)溺职,就我而言,我喜歡使用SwiftyJSON或則ObjectMapper來(lái)進(jìn)行json轉(zhuǎn)模型位喂,因?yàn)橄啾仍母ㄔ福褂眠@些第三方會(huì)給我們帶來(lái)更高的效率。于是在Swift4中忆某,Apple官方就此提供了自己的方法点待,現(xiàn)在我們來(lái)了解其基本的用法。

JSONSerialization:(iOS之蘋(píng)果自帶的json解析NSJSONSerialization(序列化))

NSJSONSerialization提供了將JSON數(shù)據(jù)轉(zhuǎn)換為Foundation對(duì)象(一般都是NSDictionaryNSArray)和Foundation對(duì)象轉(zhuǎn)換為JSON數(shù)據(jù)(可以通過(guò)調(diào)用isValidJSONObject來(lái)判斷Foundation對(duì)象是否可以轉(zhuǎn)換為JSON數(shù)據(jù))

Codable的簡(jiǎn)單使用

首先弃舒,我們來(lái)對(duì)最簡(jiǎn)單的json數(shù)據(jù)進(jìn)行轉(zhuǎn)模型癞埠,現(xiàn)在我們有以下一組json數(shù)據(jù):

let res = """
{
    "name": "Jone",
    "age": 17
}
"""
let data = res.data(using: .utf8)!

然后我們定義一個(gè)Student結(jié)構(gòu)體作為數(shù)據(jù)的模型,并遵守Codable協(xié)議:

struct Student: Codable {
    let name: String
    let age: Int
}

而關(guān)于Codable協(xié)議的描述我們可以點(diǎn)進(jìn)去看一下:

public typealias Codable = Decodable & Encodable

public protocol Encodable {
    public func encode(to encoder: Encoder) throws
}

public protocol Decodable {
    public init(from decoder: Decoder) throws
}

其實(shí)就是遵守一個(gè)關(guān)于解碼的協(xié)議和一個(gè)關(guān)于編碼的協(xié)議聋呢,只要遵守這些協(xié)議才能進(jìn)行json與模型之間的編碼與解碼苗踪。
接下來(lái)我們就可以進(jìn)行講json解碼并映射成模型:

let decoder = JSONDecoder()
let stu = try! decoder.decode(Student.self, from: data)
print(stu) //Student(name: "Jone", age: 17)

然后,我們可以將剛才得到的模型進(jìn)行編碼成json:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted //輸出格式好看點(diǎn)
let data = try! encoder.encode(stu)
print(String(data: data, encoding: .utf8)!)
//{
//    "name" : "Jone",
//    "age" : 17
//}

就是這么簡(jiǎn)單~~~

這里對(duì)encode和decode使用try!是為了減少文章篇幅削锰,正常使用時(shí)要對(duì)錯(cuò)誤進(jìn)行處理通铲,而常見(jiàn)的錯(cuò)誤會(huì)在第三篇講到。

json中的key和模型中的Key不對(duì)應(yīng)

通常器贩,我們從服務(wù)器獲取到的json里面的key命名方式和我們是有區(qū)別的颅夺,后臺(tái)對(duì)一些key的命名方式喜歡用下劃線來(lái)分割單詞,而我們更習(xí)慣于使用駝峰命名法蛹稍,例如這樣的情況:

let res = """
{
    "name": "Jone",
    "age": 17,
    "born_in": "China"
}
"""
let data = res.data(using: .utf8)!

struct Student: Codable {
    let name: String
    let age: Int
    let bornIn: String
}

顯然吧黄,在映射成模型的過(guò)程中會(huì)因?yàn)閖son中key與屬性名稱對(duì)不上而報(bào)錯(cuò),而此時(shí)我們就可以使用CodingKey這個(gè)protocol來(lái)規(guī)確定屬性名和json中的key的映射規(guī)則唆姐,我們現(xiàn)在看看這個(gè)協(xié)議:

public protocol CodingKey {
    public var stringValue: String { get }
    public init?(stringValue: String)
    public var intValue: Int? { get }
    public init?(intValue: Int)
}

而實(shí)現(xiàn)這個(gè)功能最簡(jiǎn)單的方式是使用一個(gè)enum來(lái)遵守這個(gè)協(xié)議并且會(huì)自動(dòng)實(shí)現(xiàn)這個(gè)protocol里的方法拗慨,使用case來(lái)指定映射規(guī)則:

struct Student: Codable {
    let name: String
    let age: Int
    let bornIn: String

    enum CodingKeys: String, CodingKey {
        case name
        case age
        case bornIn = "born_in"
    }
}

現(xiàn)在就能很好的工作了

let decoder = JSONDecoder()
let stu = try! decoder.decode(Student.self, from: json)
print(stu)  //Student(name: "Jone", age: 17, bornIn: "China")

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted //輸出格式好看點(diǎn)
let data = try! encoder.encode(stu)
print(String(data: data, encoding: .utf8)!)
//{
//    "name" : "Jone",
//    "age" : 17,
//    "born_in" : "China"
//}

處理JSON中的日期格式,浮點(diǎn)數(shù)奉芦,base64編碼赵抢,URL

日期格式

現(xiàn)在我們就上個(gè)模型做一些簡(jiǎn)化,并添加一個(gè)新的屬性用于表示入學(xué)的注冊(cè)時(shí)間:

struct Student: Codable {
    let registerTime: Date
    
    enum CodingKeys: String, CodingKey {
        case registerTime = "register_time"
    }
}

let stu = Student(registerTime: Date())
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedData = try encoder.encode(stu)
print(String(data: encodedData, encoding: .utf8)!)
//{
//    "register_time" : 532248572.46527803
//}

如果我們不想時(shí)間以浮點(diǎn)數(shù)的形式來(lái)出現(xiàn)声功,我們可以對(duì)encoder的dateEncodingStrategy屬性進(jìn)行一些設(shè)置:

encoder.dateEncodingStrategy = .iso8601
// "register_time" : "2017-11-13T06:48:40Z"
let formatter = DateFormatter()
formatter.dateFormat = "MMM-dd-yyyy HH:mm:ss zzz"
encoder.dateEncodingStrategy = .formatted(formatter)
// "register_time" : "Nov-13-2017 14:55:30 GMT+8"

浮點(diǎn)數(shù)

有時(shí)服務(wù)器返回一個(gè)數(shù)據(jù)是一些特殊值時(shí)烦却,例如返回的學(xué)生高度的數(shù)值是一個(gè)NaN,這時(shí)我們對(duì)decodernonConformingFloatDecodingStrategy屬性進(jìn)行設(shè)置:

struct Student: Codable {
    let height: Float
}

let res = """
{
    "height": "NaN"
}
"""
let json = res.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "+∞", negativeInfinity: "-∞", nan: "NaN")
print((try! decoder.decode(Student.self, from: json)).height) //nan

base64編碼

有時(shí)服務(wù)器返回一個(gè)base64編碼的數(shù)據(jù)時(shí)减噪,我們對(duì)decoderdataDecodingStrategy屬性進(jìn)行設(shè)置:

struct Student: Codable {
    let blog: Data
}

let res = """
{
    "blog": "aHR0cDovL3d3dy5qaWFuc2h1LmNvbS91c2Vycy8zMjhmNWY5ZDBiNTgvdGltZWxpbmU="
}
"""
let json = res.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
let stu = try! decoder.decode(Student.self, from: json)
print(String(data: stu.blog, encoding: .utf8)!)
// http://www.reibang.com/users/328f5f9d0b58/timeline

URL

而對(duì)于URL來(lái)說(shuō)短绸,直接映射就可以了

struct Student: Codable {
    let blogUrl: URL
}
let res = """
{
    "blogUrl": "http://www.reibang.com/users/328f5f9d0b58/timeline"
}
"""
let json = res.data(using: .utf8)!
let decoder = JSONDecoder()
print(try! decoder.decode(Student.self, from: json).blogUrl)
// http://www.reibang.com/users/328f5f9d0b58/timeline

處理常見(jiàn)的JSON嵌套結(jié)構(gòu)

在此之前,因?yàn)樵趈son和模型之間轉(zhuǎn)換的過(guò)程是類似的筹裕,為了節(jié)約時(shí)間醋闭,先定義兩個(gè)泛型函數(shù)用于encodedecode:

func encode<T>(of model: T) throws where T: Codable {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    let encodedData = try encoder.encode(model)
    print(String(data: encodedData, encoding: .utf8)!)
}
func decode<T>(of jsonString: String, type: T.Type) throws -> T where T: Codable {
    let data = jsonString.data(using: .utf8)!
    let decoder = JSONDecoder()
    let model = try! decoder.decode(T.self, from: data)
    return model
}

用對(duì)象封裝數(shù)組

對(duì)于使用一個(gè)對(duì)象來(lái)封裝了一數(shù)組的json:

let res = """
{
    "students" : [
        {
            "name": "ZhangSan",
            "age": 17,
            "sex": "male",
            "born_in": "China"
        },
        {
            "name": "LiSi",
            "age": 18,
            "sex": "male",
            "born_in": "Japan"
        },
        {
            "name": "WangWu",
            "age": 16,
            "sex": "male",
            "born_in": "USA"
        }
    ]
}
"""

對(duì)于這類json,我們只需要定義一個(gè)類型朝卒,該類型包含一個(gè)數(shù)組证逻,數(shù)組類型就是這些內(nèi)嵌類型

struct Classes: Codable {
    let students: [Student]
    
    struct Student: Codable {
        let name: String
        let age: Int
        let sex: SexType
        let bornIn: String
        
        enum SexType: String, Codable {
            case male
            case female
        }
        
        enum CodingKeys: String, CodingKey {
            case name
            case age
            case sex
            case bornIn = "born_in"
        }
    }
}
let c = try! decode(of: res, type: Classes.self)
dump(c)
try! encode(of: c)

數(shù)組作為JSON根對(duì)象

如果服務(wù)器返回來(lái)的數(shù)據(jù)如果是一個(gè)數(shù)組,而數(shù)組里面的是一個(gè)個(gè)對(duì)象的字典:

let res = """
[
    {
        "name": "ZhangSan",
        "age": 17,
        "sex": "male",
        "born_in": "China"
    },
    {
        "name": "LiSi",
        "age": 18,
        "sex": "male",
        "born_in": "Japan"
    },
    {
        "name": "WangWu",
        "age": 16,
        "sex": "male",
        "born_in": "USA"
    }
]
"""

對(duì)于這種類型抗斤,我們也將它轉(zhuǎn)化了一個(gè)數(shù)組囚企,數(shù)組的類型就是json數(shù)組中字典所代表的對(duì)象類型

struct Student: Codable {
    let name: String
    let age: Int
    let sex: SexType
    let bornIn: String
    
    enum SexType: String, Codable {
        case male
        case female
    }
    
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case sex
        case bornIn = "born_in"
    }
}

let stu = try! decode(of: res, type: [Student].self)
dump(stu)
try! encode(of: stu)

純數(shù)組中的對(duì)象帶有唯一Key

如果數(shù)據(jù)是由多個(gè)字典組成的數(shù)組,字典里又有一組鍵值對(duì)瑞眼,這種格式可以看成是前兩種的組合:

let res = """
[
    {
        "student": {
            "name": "ZhangSan",
            "age": 17,
            "sex": "male",
            "born_in": "China"
        }
    },
    {
        "student": {
            "name": "LiSi",
            "age": 18,
            "sex": "male",
            "born_in": "Japan"
        }
    },
    {
        "student": {
            "name": "WangWu",
            "age": 16,
            "sex": "male",
            "born_in": "USA"
        }
    }
]
"""

解析這種數(shù)據(jù)龙宏,我們像第二種方式一樣,對(duì)于外圍的數(shù)組我們只需要在內(nèi)層的類型中加上一個(gè)中括號(hào)就可以了伤疙,而里面的類型這里我們需要定義成Dictionary<String, Student>:

struct Student: Codable {
    let name: String
    let age: Int
    let sex: SexType
    let bornIn: String
    
    enum SexType: String, Codable {
        case male
        case female
    }
    
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case sex
        case bornIn = "born_in"
    }
}
let stu = try! decode(of: res, type: [Dictionary<String, Student>].self)
dump(stu)
try! encode(of: stu)

更一般的復(fù)雜情況

接下來(lái)我們看一種類型银酗,對(duì)于這種類型相對(duì)之前更復(fù)雜,但處理起來(lái)也是很簡(jiǎn)單徒像,日常開(kāi)發(fā)中也是接觸最多這種情況:

let res = """
{
    "info": {
        "grade": "3",
        "classes": "1112"
    },
    "students" : [
        {
            "name": "ZhangSan",
            "age": 17,
            "sex": "male",
            "born_in": "China"
        },
        {
            "name": "LiSi",
            "age": 18,
            "sex": "male",
            "born_in": "Japan"
        },
        {
            "name": "WangWu",
            "age": 16,
            "sex": "male",
            "born_in": "USA"
        }
    ]
}
"""

我們按照老套路一個(gè)一個(gè)來(lái)定制模型其實(shí)也是很簡(jiǎn)單的:

struct Response: Codable {
    let info: Info
    let students: [Student]
    
    struct Info: Codable {
        let grade: String
        let classes: String
    }
    
    struct Student: Codable {
        let name: String
        let age: Int
        let sex: SexType
        let bornIn: String
        
        enum SexType: String, Codable {
            case male
            case female
        }
        
        enum CodingKeys: String, CodingKey {
            case name
            case age
            case sex
            case bornIn = "born_in"
        }
    }
}
let response = try! decode(of: res, type: Response.self)
dump(response)
try! encode(of: response)

Swift 4.1 / 2018.04.03

apple在3.29更新了Swift 4.1黍特,其中對(duì)Codable進(jìn)行了優(yōu)化。在文章上面的內(nèi)容中锯蛀,我們使用CodingKeysenum來(lái)將屬性名與json不一致的key來(lái)進(jìn)行映射灭衷。不過(guò)大家是否有發(fā)現(xiàn),不一致是因?yàn)閖son的key是用下劃線分割單詞旁涤,而我們的屬性命名是用駝峰命名法翔曲。于是Swift4.1中我們可以為JSONEncoderkeyEncodingStrategy屬性設(shè)置為convertToSnakeCaseJSONDecoderkeyDecodingStrategy屬性設(shè)置為convertFromSnakeCase。這樣我們就不用寫(xiě)CodingKeys來(lái)處理這個(gè)問(wèn)題了劈愚。

struct Person: Codable {
    let name: String
    let age: Int
    let bornIn: String
}
let ming = Person(name: "ming", age: 18, bornIn: "China")
let alex = Person(name: "alex", age: 22, bornIn: "USA")
let people1 = [ming, alex]

let encoder = JSONEncoder()
// 設(shè)置為 convertToSnakeCase
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted

let jsonData = try! encoder.encode(people1)
if let jsonstr = String(data: jsonData, encoding: .utf8) {
    print(jsonstr)
    /** 將駝峰命名法變成了用下劃線分割
     [
         {
             "name" : "ming",
             "age" : 18,
             "born_in" : "China"
         },
         {
             "name" : "alex",
             "age" : 22,
             "born_in" : "USA"
         }
     ]
     */
}

let decoder = JSONDecoder()
// 設(shè)置為 convertFromSnakeCase
decoder.keyDecodingStrategy = .convertFromSnakeCase
let people2 = try! decoder.decode([Person].self, from: jsonData)
dump(people2)
/** 將用下劃線分割的key映射為用駝峰命名法命名的屬性
 ? 2 elements
     ? Swift_4_1_Codable.Person
         - name: "ming"
         - age: 18
         - bornIn: "China"
     ? Swift_4_1_Codable.Person
         - name: "alex"
         - age: 22
         - bornIn: "USA"
 */

在日常開(kāi)發(fā)中部默,后端傳過(guò)來(lái)json中的key大部分是用下劃線分割的,因此在Swift4的時(shí)候我們使用Codable時(shí)候還要寫(xiě)那繁瑣的CodingKeys造虎,到了Swift4.1傅蹂,我們就不用寫(xiě)了??。

至此算凿,我們對(duì)Codable的基本使用已經(jīng)熟悉了份蝴,只要遵守了Codable協(xié)議就能享受其帶來(lái)的編碼與解碼的便利,若我們需要制定key和屬性名的對(duì)應(yīng)規(guī)則我們就需要使用CodingKey協(xié)議氓轰,對(duì)于日常開(kāi)發(fā)中能滿足我們大部分的需求婚夫,但也只是大部分,因?yàn)檫€有時(shí)候我們需要對(duì)數(shù)據(jù)進(jìn)行一些處理署鸡,這是我們就需要自定義其編碼與解碼的過(guò)程了案糙,下一篇我將介紹更多Codable協(xié)議的內(nèi)容限嫌。

Swift JSON編碼時(shí)支持Camel Case和Snake Case之間的轉(zhuǎn)換

Swift 4.0引入了Codable,但是有個(gè)麻煩的問(wèn)題:如果JSON數(shù)據(jù)的key命名格式是snake_case的話时捌,我們必須創(chuàng)建自己的CodingKeys來(lái)告訴蘋(píng)果怎么轉(zhuǎn)換怒医。

但是在Swift 4.1中,蘋(píng)果給JSONDecoder引入了一個(gè)屬性keyDecodingStrategy奢讨;對(duì)應(yīng)的JSONEncoder引入了一個(gè)屬性keyEncodingStrategy稚叹。這樣我們就不需要設(shè)置定義CodingKeys了。只需要在decoding的時(shí)候把keyDecodingStrategy設(shè)置為.convertFromSnakeCase拿诸;在encoding的時(shí)候把keyEncodingStrategy設(shè)置為.convertToSnakeCase

屬性名策略說(shuō)明:

CamelCase策略扒袖,對(duì)象屬性:personId,序列化后屬性:persionId

PascalCase策略亩码,對(duì)象屬性:personId季率,序列化后屬性:PersonId

SnakeCase策略,對(duì)象屬性:personId描沟,序列化后屬性:person_id

KebabCase策略蚀同,對(duì)象屬性:personId,序列化后屬性:person-id

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啊掏,一起剝皮案震驚了整個(gè)濱河市蠢络,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迟蜜,老刑警劉巖刹孔,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異娜睛,居然都是意外死亡髓霞,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)畦戒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)方库,“玉大人,你說(shuō)我怎么就攤上這事障斋∽萘剩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵垃环,是天一觀的道長(zhǎng)邀层。 經(jīng)常有香客問(wèn)我,道長(zhǎng)遂庄,這世上最難降的妖魔是什么寥院? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮涛目,結(jié)果婚禮上秸谢,老公的妹妹穿的比我還像新娘凛澎。我一直安慰自己,他們只是感情好估蹄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布塑煎。 她就那樣靜靜地躺著,像睡著了一般元媚。 火紅的嫁衣襯著肌膚如雪轧叽。 梳的紋絲不亂的頭發(fā)上苗沧,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天刊棕,我揣著相機(jī)與錄音,去河邊找鬼待逞。 笑死甥角,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的识樱。 我是一名探鬼主播嗤无,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怜庸!你這毒婦竟也來(lái)了当犯?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤割疾,失蹤者是張志新(化名)和其女友劉穎嚎卫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宏榕,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拓诸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了麻昼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奠支。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抚芦,靈堂內(nèi)的尸體忽然破棺而出倍谜,到底是詐尸還是另有隱情,我是刑警寧澤叉抡,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布枢劝,位于F島的核電站,受9級(jí)特大地震影響卜壕,放射性物質(zhì)發(fā)生泄漏您旁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一轴捎、第九天 我趴在偏房一處隱蔽的房頂上張望鹤盒。 院中可真熱鬧蚕脏,春花似錦、人聲如沸侦锯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尺碰。三九已至挣棕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亲桥,已是汗流浹背洛心。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留题篷,地道東北人词身。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像番枚,于是被迫代替她去往敵國(guó)和親法严。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容