Go-JSON處理

原文地址:https://www.liwenzhou.com/posts/Go/json_tricks_in_go/

本文總結(jié)了我平時在項目中遇到的那些關(guān)于go語言JSON數(shù)據(jù)與結(jié)構(gòu)體之間相互轉(zhuǎn)換的問題及解決辦法七芭。

基本的序列化

首先我們來看一下Go語言中json.Marshal()(系列化)與json.Unmarshal(反序列化)的基本用法汽馋。

type Person struct {
    Name   string
    Age    int64
    Weight float64
}

func main() {
    p1 := Person{
        Name:   "七米",
        Age:    18,
        Weight: 71.5,
    }
    // struct -> json string
    b, err := json.Marshal(p1)
    if err != nil {
        fmt.Printf("json.Marshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
    // json string -> struct
    var p2 Person
    err = json.Unmarshal(b, &p2)
    if err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("p2:%#v\n", p2)
}

輸出:

str:{"Name":"七米","Age":18,"Weight":71.5}
p2:main.Person{Name:"七米", Age:18, Weight:71.5}

結(jié)構(gòu)體tag介紹

Tag是結(jié)構(gòu)體的元信息风题,可以在運行的時候通過反射的機制讀取出來丘喻。 Tag在結(jié)構(gòu)體字段的后方定義,由一對反引號包裹起來臂寝,具體的格式如下:

`key1:"value1" key2:"value2"`

結(jié)構(gòu)體tag由一個或多個鍵值對組成蝴悉。鍵與值使用冒號分隔,值用雙引號括起來凭涂。同一個結(jié)構(gòu)體字段可以設(shè)置多個鍵值對tag,不同的鍵值對之間使用空格分隔贴妻。

使用json tag指定字段名

序列化與反序列化默認情況下使用結(jié)構(gòu)體的字段名切油,我們可以通過給結(jié)構(gòu)體字段添加tag來指定json序列化生成的字段名。

// 使用json tag指定序列化與反序列化時的行為
type Person struct {
    Name   string `json:"name"` // 指定json序列化/反序列化時使用小寫name
    Age    int64
    Weight float64
}

忽略某個字段

如果你想在json序列化/反序列化的時候忽略掉結(jié)構(gòu)體中的某個字段名惩,可以按如下方式在tag中添加-澎胡。

// 使用json tag指定json序列化與反序列化時的行為
type Person struct {
    Name   string `json:"name"` // 指定json序列化/反序列化時使用小寫name
    Age    int64
    Weight float64 `json:"-"` // 指定json序列化/反序列化時忽略此字段
}

忽略空值字段

當(dāng) struct 中的字段沒有值時, json.Marshal() 序列化的時候不會忽略這些字段绢片,而是默認輸出字段的類型零值(例如intfloat類型零值是 0滤馍,string類型零值是"",對象類型零值是 nil)底循。如果想要在序列序列化時忽略這些沒有值的字段時,可以在對應(yīng)字段添加omitempty tag槐瑞。

舉個例子:

type User struct {
    Name  string   `json:"name"`
    Email string   `json:"email"`
    Hobby []string `json:"hobby"`
}

func omitemptyDemo() {
    u1 := User{
        Name: "七米",
    }
    // struct -> json string
    b, err := json.Marshal(u1)
    if err != nil {
        fmt.Printf("json.Marshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
}

輸出結(jié)果:

str:{"name":"七米","email":"","hobby":null}

如果想要在最終的序列化結(jié)果中去掉空值字段熙涤,可以像下面這樣定義結(jié)構(gòu)體:

// 在tag中添加omitempty忽略空值
// 注意這里 hobby,omitempty 合起來是json tag值,中間用英文逗號分隔
type User struct {
    Name  string   `json:"name"`
    Email string   `json:"email,omitempty"`
    Hobby []string `json:"hobby,omitempty"`
}

此時困檩,再執(zhí)行上述的omitemptyDemo祠挫,輸出結(jié)果如下:

str:{"name":"七米"} // 序列化結(jié)果中沒有email和hobby字段

忽略嵌套結(jié)構(gòu)體空值字段

首先來看幾種結(jié)構(gòu)體嵌套的示例:

type User struct {
    Name  string   `json:"name"`
    Email string   `json:"email,omitempty"`
    Hobby []string `json:"hobby,omitempty"`
    Profile
}

type Profile struct {
    Website string `json:"site"`
    Slogan  string `json:"slogan"`
}

func nestedStructDemo() {
    u1 := User{
        Name:  "七米",
        Hobby: []string{"足球", "雙色球"},
    }
    b, err := json.Marshal(u1)
    if err != nil {
        fmt.Printf("json.Marshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
}

匿名嵌套Profile時序列化后的json串為單層的:

str:{"name":"七米","hobby":["足球","雙色球"],"site":"","slogan":""}

想要變成嵌套的json串,需要改為具名嵌套或定義字段tag:

type User struct {
    Name    string   `json:"name"`
    Email   string   `json:"email,omitempty"`
    Hobby   []string `json:"hobby,omitempty"`
    Profile `json:"profile"`
}
// str:{"name":"七米","hobby":["足球","雙色球"],"profile":{"site":"","slogan":""}}

想要在嵌套的結(jié)構(gòu)體為空值時悼沿,忽略該字段等舔,僅添加omitempty是不夠的:

type User struct {
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"`
    Hobby    []string `json:"hobby,omitempty"`
    Profile `json:"profile,omitempty"`
}
// str:{"name":"七米","hobby":["足球","雙色球"],"profile":{"site":"","slogan":""}}

還需要使用嵌套的結(jié)構(gòu)體指針:

type User struct {
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"`
    Hobby    []string `json:"hobby,omitempty"`
    *Profile `json:"profile,omitempty"`
}
// str:{"name":"七米","hobby":["足球","雙色球"]}

不修改原結(jié)構(gòu)體忽略空值字段

我們需要json序列化User,但是不想把密碼也序列化糟趾,又不想修改User結(jié)構(gòu)體慌植,這個時候我們就可以使用創(chuàng)建另外一個結(jié)構(gòu)體PublicUser匿名嵌套原User甚牲,同時指定Password字段為匿名結(jié)構(gòu)體指針類型,并添加omitemptytag蝶柿,示例代碼如下:

type User struct {
    Name     string `json:"name"`
    Password string `json:"password"`
}

type PublicUser struct {
    *User             // 匿名嵌套
    Password *struct{} `json:"password,omitempty"`
}

func omitPasswordDemo() {
    u1 := User{
        Name:     "七米",
        Password: "123456",
    }
    b, err := json.Marshal(PublicUser{User: &u1})
    if err != nil {
        fmt.Printf("json.Marshal u1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)  // str:{"name":"七米"}
}

優(yōu)雅處理字符串格式的數(shù)字

有時候丈钙,前端在傳遞來的json數(shù)據(jù)中可能會使用字符串類型的數(shù)字,這個時候可以在結(jié)構(gòu)體tag中添加string來告訴json包從字符串中解析相應(yīng)字段的數(shù)據(jù):

type Card struct {
    ID    int64   `json:"id,string"`    // 添加string tag
    Score float64 `json:"score,string"` // 添加string tag
}

func intAndStringDemo() {
    jsonStr1 := `{"id": "1234567","score": "88.50"}`
    var c1 Card
    if err := json.Unmarshal([]byte(jsonStr1), &c1); err != nil {
        fmt.Printf("json.Unmarsha jsonStr1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("c1:%#v\n", c1) // c1:main.Card{ID:1234567, Score:88.5}
}

整數(shù)變浮點數(shù)

在 JSON 協(xié)議中是沒有整型和浮點型之分的交汤,它們統(tǒng)稱為number雏赦。json字符串中的數(shù)字經(jīng)過Go語言中的json包反序列化之后都會成為float64類型。下面的代碼便演示了這個問題:

func jsonDemo() {
    // map[string]interface{} -> json string
    var m = make(map[string]interface{}, 1)
    m["count"] = 1 // int
    b, err := json.Marshal(m)
    if err != nil {
        fmt.Printf("marshal failed, err:%v\n", err)
    }
    fmt.Printf("str:%#v\n", string(b))
    // json string -> map[string]interface{}
    var m2 map[string]interface{}
    err = json.Unmarshal(b, &m2)
    if err != nil {
        fmt.Printf("unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("value:%v\n", m2["count"]) // 1
    fmt.Printf("type:%T\n", m2["count"])  // float64
}

這種場景下如果想更合理的處理數(shù)字就需要使用decoder去反序列化芙扎,示例代碼如下:

func decoderDemo() {
    // map[string]interface{} -> json string
    var m = make(map[string]interface{}, 1)
    m["count"] = 1 // int
    b, err := json.Marshal(m)
    if err != nil {
        fmt.Printf("marshal failed, err:%v\n", err)
    }
    fmt.Printf("str:%#v\n", string(b))
    // json string -> map[string]interface{}
    var m2 map[string]interface{}
    // 使用decoder方式反序列化星岗,指定使用number類型
    decoder := json.NewDecoder(bytes.NewReader(b))
    decoder.UseNumber()
    err = decoder.Decode(&m2)
    if err != nil {
        fmt.Printf("unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("value:%v\n", m2["count"]) // 1
    fmt.Printf("type:%T\n", m2["count"])  // json.Number
    // 將m2["count"]轉(zhuǎn)為json.Number之后調(diào)用Int64()方法獲得int64類型的值
    count, err := m2["count"].(json.Number).Int64()
    if err != nil {
        fmt.Printf("parse to int64 failed, err:%v\n", err)
        return
    }
    fmt.Printf("type:%T\n", int(count)) // int
}

json.Number的源碼定義如下:

// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String() string { return string(n) }

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
    return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
    return strconv.ParseInt(string(n), 10, 64)
}

我們在處理number類型的json字段時需要先得到json.Number類型,然后根據(jù)該字段的實際類型調(diào)用Float64()Int64()戒洼。

自定義解析時間字段

Go語言內(nèi)置的 json 包使用 RFC3339 標(biāo)準(zhǔn)中定義的時間格式俏橘,對我們序列化時間字段的時候有很多限制。

type Post struct {
    CreateTime time.Time `json:"create_time"`
}

func timeFieldDemo() {
    p1 := Post{CreateTime: time.Now()}
    b, err := json.Marshal(p1)
    if err != nil {
        fmt.Printf("json.Marshal p1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
    jsonStr := `{"create_time":"2020-04-05 12:25:42"}`
    var p2 Post
    if err := json.Unmarshal([]byte(jsonStr), &p2); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("p2:%#v\n", p2)
}

上面的代碼輸出結(jié)果如下:

str:{"create_time":"2020-04-05T12:28:06.799214+08:00"}
json.Unmarshal failed, err:parsing time ""2020-04-05 12:25:42"" as ""2006-01-02T15:04:05Z07:00"": cannot parse " 12:25:42"" as "T"

也就是內(nèi)置的json包不識別我們常用的字符串時間格式施逾,如2020-04-05 12:25:42敷矫。

不過我們通過實現(xiàn) json.Marshaler/json.Unmarshaler 接口實現(xiàn)自定義的事件格式解析。

type CustomTime struct {
    time.Time
}

const ctLayout = "2006-01-02 15:04:05"

var nilTime = (time.Time{}).UnixNano()

func (ct *CustomTime) UnmarshalJSON(b []byte) (err error) {
    s := strings.Trim(string(b), "\"")
    if s == "null" {
        ct.Time = time.Time{}
        return
    }
    ct.Time, err = time.Parse(ctLayout, s)
    return
}

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    if ct.Time.UnixNano() == nilTime {
        return []byte("null"), nil
    }
    return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(ctLayout))), nil
}

func (ct *CustomTime) IsSet() bool {
    return ct.UnixNano() != nilTime
}

type Post struct {
    CreateTime CustomTime `json:"create_time"`
}

func timeFieldDemo() {
    p1 := Post{CreateTime: CustomTime{time.Now()}}
    b, err := json.Marshal(p1)
    if err != nil {
        fmt.Printf("json.Marshal p1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
    jsonStr := `{"create_time":"2020-04-05 12:25:42"}`
    var p2 Post
    if err := json.Unmarshal([]byte(jsonStr), &p2); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("p2:%#v\n", p2)
}

自定義MarshalJSON和UnmarshalJSON方法

上面那種自定義類型的方法稍顯啰嗦了一點汉额,下面來看一種相對便捷的方法曹仗。

首先你需要知道的是,如果你能夠為某個類型實現(xiàn)了MarshalJSON()([]byte, error)UnmarshalJSON(b []byte) error方法蠕搜,那么這個類型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)時就會使用你定制的相應(yīng)方法怎茫。

type Order struct {
    ID          int       `json:"id"`
    Title       string    `json:"title"`
    CreatedTime time.Time `json:"created_time"`
}

const layout = "2006-01-02 15:04:05"

// MarshalJSON 為Order類型實現(xiàn)自定義的MarshalJSON方法
func (o *Order) MarshalJSON() ([]byte, error) {
    type TempOrder Order // 定義與Order字段一致的新類型
    return json.Marshal(struct {
        CreatedTime string `json:"created_time"`
        *TempOrder         // 避免直接嵌套Order進入死循環(huán)
    }{
        CreatedTime: o.CreatedTime.Format(layout),
        TempOrder:   (*TempOrder)(o),
    })
}

// UnmarshalJSON 為Order類型實現(xiàn)自定義的UnmarshalJSON方法
func (o *Order) UnmarshalJSON(data []byte) error {
    type TempOrder Order // 定義與Order字段一致的新類型
    ot := struct {
        CreatedTime string `json:"created_time"`
        *TempOrder         // 避免直接嵌套Order進入死循環(huán)
    }{
        TempOrder: (*TempOrder)(o),
    }
    if err := json.Unmarshal(data, &ot); err != nil {
        return err
    }
    var err error
    o.CreatedTime, err = time.Parse(layout, ot.CreatedTime)
    if err != nil {
        return err
    }
    return nil
}

// 自定義序列化方法
func customMethodDemo() {
    o1 := Order{
        ID:          123456,
        Title:       "《七米的Go學(xué)習(xí)筆記》",
        CreatedTime: time.Now(),
    }
    // 通過自定義的MarshalJSON方法實現(xiàn)struct -> json string
    b, err := json.Marshal(&o1)
    if err != nil {
        fmt.Printf("json.Marshal o1 failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
    // 通過自定義的UnmarshalJSON方法實現(xiàn)json string -> struct
    jsonStr := `{"created_time":"2020-04-05 10:18:20","id":123456,"title":"《七米的Go學(xué)習(xí)筆記》"}`
    var o2 Order
    if err := json.Unmarshal([]byte(jsonStr), &o2); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("o2:%#v\n", o2)
}

輸出結(jié)果:

str:{"created_time":"2020-04-05 10:32:20","id":123456,"title":"《七米的Go學(xué)習(xí)筆記》"}
o2:main.Order{ID:123456, Title:"《七米的Go學(xué)習(xí)筆記》", CreatedTime:time.Time{wall:0x0, ext:63721678700, loc:(*time.Location)(nil)}}

使用匿名結(jié)構(gòu)體添加字段

使用內(nèi)嵌結(jié)構(gòu)體能夠擴展結(jié)構(gòu)體的字段,但有時候我們沒有必要單獨定義新的結(jié)構(gòu)體妓灌,可以使用匿名結(jié)構(gòu)體簡化操作:

type UserInfo struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func anonymousStructDemo() {
    u1 := UserInfo{
        ID:   123456,
        Name: "七米",
    }
    // 使用匿名結(jié)構(gòu)體內(nèi)嵌User并添加額外字段Token
    b, err := json.Marshal(struct {
        *UserInfo
        Token string `json:"token"`
    }{
        &u1,
        "91je3a4s72d1da96h",
    })
    if err != nil {
        fmt.Printf("json.Marsha failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
    // str:{"id":123456,"name":"七米","token":"91je3a4s72d1da96h"}
}

使用匿名結(jié)構(gòu)體組合多個結(jié)構(gòu)體

同理轨蛤,也可以使用匿名結(jié)構(gòu)體來組合多個結(jié)構(gòu)體來序列化與反序列化數(shù)據(jù):

type Comment struct {
    Content string
}

type Image struct {
    Title string `json:"title"`
    URL   string `json:"url"`
}

func anonymousStructDemo2() {
    c1 := Comment{
        Content: "永遠不要高估自己",
    }
    i1 := Image{
        Title: "贊賞碼",
        URL:   "https://www.liwenzhou.com/images/zanshang_qr.jpg",
    }
    // struct -> json string
    b, err := json.Marshal(struct {
        *Comment
        *Image
    }{&c1, &i1})
    if err != nil {
        fmt.Printf("json.Marshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("str:%s\n", b)
    // json string -> struct
    jsonStr := `{"Content":"永遠不要高估自己","title":"贊賞碼","url":"https://www.liwenzhou.com/images/zanshang_qr.jpg"}`
    var (
        c2 Comment
        i2 Image
    )
    if err := json.Unmarshal([]byte(jsonStr), &struct {
        *Comment
        *Image
    }{&c2, &i2}); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("c2:%#v i2:%#v\n", c2, i2)
}

輸出:

str:{"Content":"永遠不要高估自己","title":"贊賞碼","url":"https://www.liwenzhou.com/images/zanshang_qr.jpg"}
c2:main.Comment{Content:"永遠不要高估自己"} i2:main.Image{Title:"贊賞碼", URL:"https://www.liwenzhou.com/images/zanshang_qr.jpg"}

處理不確定層級的json

如果json串沒有固定的格式導(dǎo)致不好定義與其相對應(yīng)的結(jié)構(gòu)體時,我們可以使用json.RawMessage原始字節(jié)數(shù)據(jù)保存下來虫埂。

type sendMsg struct {
    User string `json:"user"`
    Msg  string `json:"msg"`
}

func rawMessageDemo() {
    jsonStr := `{"sendMsg":{"user":"q1mi","msg":"永遠不要高估自己"},"say":"Hello"}`
    // 定義一個map祥山,value類型為json.RawMessage,方便后續(xù)更靈活地處理
    var data map[string]json.RawMessage
    if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
        fmt.Printf("json.Unmarshal jsonStr failed, err:%v\n", err)
        return
    }
    var msg sendMsg
    if err := json.Unmarshal(data["sendMsg"], &msg); err != nil {
        fmt.Printf("json.Unmarshal failed, err:%v\n", err)
        return
    }
    fmt.Printf("msg:%#v\n", msg)
    // msg:main.sendMsg{User:"q1mi", Msg:"永遠不要高估自己"}
}

參考鏈接:

https://stackoverflow.com/questions/25087960/json-unmarshal-time-that-isnt-in-rfc-3339-format

https://colobu.com/2017/06/21/json-tricks-in-Go/

https://stackoverflow.com/questions/11066946/partly-json-unmarshal-into-a-map-in-go

http://choly.ca/post/go-json-marshalling/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掉伏,一起剝皮案震驚了整個濱河市缝呕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斧散,老刑警劉巖供常,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸡捐,居然都是意外死亡栈暇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門箍镜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來源祈,“玉大人煎源,你說我怎么就攤上這事⌒虏” “怎么了薪夕?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赫悄。 經(jīng)常有香客問我原献,道長,這世上最難降的妖魔是什么埂淮? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任姑隅,我火速辦了婚禮,結(jié)果婚禮上倔撞,老公的妹妹穿的比我還像新娘讲仰。我一直安慰自己,他們只是感情好痪蝇,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布鄙陡。 她就那樣靜靜地躺著,像睡著了一般躏啰。 火紅的嫁衣襯著肌膚如雪趁矾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天给僵,我揣著相機與錄音毫捣,去河邊找鬼。 笑死帝际,一個胖子當(dāng)著我的面吹牛蔓同,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹲诀,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼斑粱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脯爪?” 一聲冷哼從身側(cè)響起珊佣,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎披粟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冷冗,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡守屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒿辙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拇泛。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡滨巴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俺叭,到底是詐尸還是另有隱情恭取,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布熄守,位于F島的核電站蜈垮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏裕照。R本人自食惡果不足惜攒发,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晋南。 院中可真熱鬧惠猿,春花似錦、人聲如沸负间。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽政溃。三九已至趾访,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玩祟,已是汗流浹背腹缩。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留空扎,地道東北人藏鹊。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像转锈,于是被迫代替她去往敵國和親盘寡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

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