Flutter的Json數(shù)據(jù)解析之FlutterJsonBeanFactory插件

一移国、FlutterJsonBeanFactory

Flutter 使用的是 Dart 語(yǔ)言進(jìn)行開(kāi)發(fā)召庞,而 Dart 語(yǔ)言沒(méi)有反射,所以無(wú)法像 Java 一樣通過(guò)反射直接將 Json 數(shù)據(jù)映射為對(duì)應(yīng)的對(duì)象實(shí)體類(lèi)對(duì)象。官方解決方案是將 Json 數(shù)據(jù)轉(zhuǎn)換為字典构蹬,然后從字典中進(jìn)行取數(shù)使用惋鹅。但直接從字典中取數(shù)很不方便则酝,寫(xiě)代碼時(shí)沒(méi)有自動(dòng)提示很不友好,而且可能在寫(xiě)的時(shí)候?qū)戝e(cuò)字段名闰集。

我們可以將 Json 轉(zhuǎn)換為字典后再映射到對(duì)象實(shí)體字段里沽讹,這樣使用時(shí)就可以直接使用對(duì)應(yīng)實(shí)體類(lèi)對(duì)象般卑。而FlutterJsonBean就可以幫我們自動(dòng)生成映射代碼。

二爽雄、插件安裝

插件市場(chǎng)搜索FlutterJsonBeanFactory蝠检,安轉(zhuǎn)后重啟AS。我安裝的版本是4.4.5挚瘟,實(shí)例代碼也是基于此版本的分析叹谁。
并且可以在Setting->Tools->FlutterJsonBeanFactory里邊自定義實(shí)體類(lèi)的后綴,默認(rèn)是entity乘盖。

三焰檩、創(chuàng)建實(shí)體類(lèi)

復(fù)制json到粘貼板,右鍵自己要存放實(shí)體的目錄订框,可以看到JsonToDartBeanAction


image.png

Class Name是實(shí)體名字析苫,會(huì)默認(rèn)加上entity
JSON TextJson文本
null-able勾選后所有屬性都是可空的?,不勾選都會(huì)加上late,延遲初始化

image.png

執(zhí)行Make后生成代碼目錄如下:
models項(xiàng)目自建布蔗,存放實(shí)體
generated/json是插件生成目錄藤违,xx_entity.g.daet是實(shí)體類(lèi)生成的輔助類(lèi)方法, base是存放基礎(chǔ)公共代碼

image.png

1.xx_entity.dart

@JsonSerializable()
class UserEntity {
  String? id;
  String? name;
  String? age;

  UserEntity();

  factory UserEntity.fromJson(Map<String, dynamic> json) =>
      $UserEntityFromJson(json);

  Map<String, dynamic> toJson() => $UserEntityToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}

會(huì)生成fromJson的工廠(chǎng)方法和toJson方法纵揍,分別調(diào)用xx_entity.g.dart中的$xxEntityFromJson方法和$xxEntityToJson方法顿乒。toSring方法會(huì)把對(duì)象轉(zhuǎn)json字符串顯示。

如果是修改了實(shí)體類(lèi)泽谨,鼠標(biāo)懸停在gernerated目錄上璧榄,執(zhí)行Alt+J快捷鍵,就會(huì)自動(dòng)生成新的映射代碼吧雹,并且去除多余的骨杂。

2.xx_entity.g.dart

實(shí)體類(lèi)對(duì)應(yīng)的輔助方法文件,g.dart是文件的后綴雄卷,存放在generated/json目錄下搓蚪。
主要包含$xxEntityFromJson$xxEntityToJson方法,$+實(shí)體類(lèi)名為前綴丁鹉。

$xxFromJson 將 Json 數(shù)據(jù)的對(duì)應(yīng)字段取出來(lái)然后賦值給實(shí)體類(lèi)的對(duì)應(yīng)字段妒潭。Json 數(shù)據(jù)轉(zhuǎn)換為實(shí)體字段使用了 jsonConvert.convert 其定義在 json_convert_content.dart 中。
$xxToJson將實(shí)體數(shù)據(jù)轉(zhuǎn)換為 Map 字典揣钦。

UserEntity $UserEntityFromJson(Map<String, dynamic> json) {
    final UserEntity userEntity = UserEntity();
    final String? id = jsonConvert.convert<String>(json['id']);
    if (id != null) {
        userEntity.id = id;
    }
    final String? user = jsonConvert.convert<String>(json['user']);
    if (user != null) {
        userEntity.user = user;
    }
    final String? age = jsonConvert.convert<String>(json['age']);
    if (age != null) {
        userEntity.age = age;
    }
    final int? sex = jsonConvert.convert<int>(json['sex']);
    if (sex != null) {
        userEntity.sex = sex;
    }
    return userEntity;
}

Map<String, dynamic> $UserEntityToJson(UserEntity entity) {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id'] = entity.id;
    data['user'] = entity.user;
    data['age'] = entity.age;
    data['sex'] = entity.sex;
    return data;
}

3.json_convert_content.dart

json_convert_content.dartJsonConvert類(lèi)雳灾, 用于統(tǒng)一進(jìn)行 Json 與實(shí)體類(lèi)的轉(zhuǎn)換。

JsonConvert jsonConvert = JsonConvert();
typedef JsonConvertFunction<T> = T Function(Map<String, dynamic> json);

class JsonConvert {
  static final Map<String, JsonConvertFunction> _convertFuncMap = {
      (UserEntity).toString(): UserEntity.fromJson,
  };

  T? convert<T>(dynamic value) {...}

  List<T?>? convertList<T>(List<dynamic>? value) {...}

  List<T>? convertListNotNull<T>(dynamic value)  {...}

  T? asT<T extends Object?>(dynamic value) {...}

  //list is returned by type
  static M? _getListChildType<M>(List<Map<String, dynamic>> data) {...}

  static M? fromJsonAsT<M>(dynamic json)  {...}
}
convert

將json數(shù)據(jù)轉(zhuǎn)換為實(shí)體對(duì)象冯凹。首先判斷了傳入的數(shù)據(jù)是否為null谎亩,為null則直接返回null, 不為空則調(diào)用asT方法。在生成的.g.dart$xxEntityFromJson方法中非 List 類(lèi)型字段基本都是調(diào)用 convert 方法進(jìn)行轉(zhuǎn)換匈庭。

  T? convert<T>(dynamic value) {
    if (value == null) {
      return null;
    }
    return asT<T>(value);
  }
convertList

將json數(shù)據(jù)轉(zhuǎn)換為實(shí)體對(duì)象List夫凸。首先也是判斷了傳入的數(shù)據(jù)是否為null ,為null則直接返回null 嚎花, 不為空則遍歷value使用map調(diào)用asT方法進(jìn)行轉(zhuǎn)換寸痢,最終還是調(diào)用的asT方法。在轉(zhuǎn)換上加了try-catch,如果報(bào)錯(cuò)則返回空的List紊选。

  List<T?>? convertList<T>(List<dynamic>? value) {
    if (value == null) {
      return null;
    }
    try {
      return value.map((dynamic e) => asT<T>(e)).toList();
    } catch (e, stackTrace) {
      debugPrint('asT<$T> $e $stackTrace');
      return <T>[];
    }
  }
converListNotNull

convertList的區(qū)別是參數(shù)不一樣,convertList參數(shù)傳入的是List<dynamic>convertListNotNull傳入的直接是dynamic啼止。其次最大的區(qū)別是調(diào)用asT方法時(shí)convertListNotNull在 asT 后面加了一個(gè) !,表示不為空兵罢。
當(dāng)在實(shí)體類(lèi)里定義字段為List類(lèi)型時(shí)献烦,會(huì)根據(jù)是否為List中元素的非空類(lèi)型而選擇生成 convertListconvertListNotNull來(lái)進(jìn)行轉(zhuǎn)換,非空采用convertListNotNull卖词,可空采用convertList

  • List<String?>? foodList1;采用convertList
  • List<String>? foodList2;采用convertListNotNull
  • late List<String?> foodList3;采用convertList
  • late List<String> foodList4;采用convertListNotNull
  List<T>? convertListNotNull<T>(dynamic value) {
    if (value == null) {
      return null;
    }
    try {
      return (value as List<dynamic>).map((dynamic e) => asT<T>(e)!).toList();
    } catch (e, stackTrace) {
      debugPrint('asT<$T> $e $stackTrace');
      return <T>[];
    }
  }
as<T>

首先判斷傳入的數(shù)據(jù)類(lèi)型是否為要轉(zhuǎn)換的數(shù)據(jù)類(lèi)型巩那,如果是的話(huà)就直接返回傳入?yún)?shù),即如果要將傳入數(shù)據(jù)轉(zhuǎn)換為User此蜈,但是傳入?yún)?shù)本身就是User類(lèi)型即横,那就直接返回。
然后通過(guò) T.String()獲取泛型類(lèi)型的名稱(chēng)裆赵,再與String东囚、intdouble战授、DateTime页藻、bool這些基礎(chǔ)數(shù)據(jù)類(lèi)型進(jìn)行比較,如果是這些類(lèi)型則調(diào)用這些類(lèi)型的轉(zhuǎn)換方法進(jìn)行轉(zhuǎn)換植兰。
最后份帐,如果不是基礎(chǔ)類(lèi)型則去_convertFuncMap尋找其它的實(shí)體類(lèi)型,并調(diào)用其value楣导,即fromJson方法废境,類(lèi)似遞歸的去解析。

  T? asT<T extends Object?>(dynamic value) {
    if (value is T) {
      return value;
    }
    final String type = T.toString();
    try {
      final String valueS = value.toString();
      if (type == "String") {
        return valueS as T;
      } else if (type == "int") {
        final int? intValue = int.tryParse(valueS);
        if (intValue == null) {
          return double.tryParse(valueS)?.toInt() as T?;
        } else {
          return intValue as T;
        }
      } else if (type == "double") {
        return double.parse(valueS) as T;
      } else if (type == "DateTime") {
        return DateTime.parse(valueS) as T;
      } else if (type == "bool") {
        if (valueS == '0' || valueS == '1') {
          return (valueS == '1') as T;
        }
        return (valueS == 'true') as T;
      } else if (type == "Map" || type.startsWith("Map<")) {
        return value as T;
      } else {
        if (_convertFuncMap.containsKey(type)) {
          return _convertFuncMap[type]!(value) as T;
        } else {
          throw UnimplementedError('$type unimplemented');
        }
      }
    } catch (e, stackTrace) {
      debugPrint('asT<$T> $e $stackTrace');
      return null;
    }
  }
fromJsonAsT

判斷傳入Json數(shù)據(jù)是否為null筒繁,為null則直接返回null彬坏。然后判斷Json數(shù)據(jù)是否為List,是 List則調(diào)用_getListChildType否則通過(guò)全局變量jsonConvert調(diào)用asT

  static M? fromJsonAsT<M>(dynamic json) {
    if (json is List) {
      return _getListChildType<M>(
          json.map((e) => e as Map<String, dynamic>).toList());
    } else {
      return jsonConvert.asT<M>(json);
    }
  }
_getListChildType

<UserEntity>[] is M直接創(chuàng)建對(duì)應(yīng)實(shí)體類(lèi)的空 List 判斷是否為泛型類(lèi)型膝晾,如果類(lèi)型相同,則通過(guò)map調(diào)用對(duì)應(yīng)實(shí)體類(lèi)的 fromJson方法進(jìn)行轉(zhuǎn)換.

  static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
    if (<UserEntity>[] is M) {
      return data
          .map<UserEntity>((Map<String, dynamic> e) => UserEntity.fromJson(e))
          .toList() as M;
    }

    debugPrint("${M.toString()} not found");

    return null;
  }

4.json_field.dart

JsonSerializable類(lèi)注解务冕,二次生成代碼時(shí)插件查找該注解的類(lèi)進(jìn)行生成血当。
JSONField字段注解,用于自定義字段映射和配置是否序列化和反序列化字段。

class JsonSerializable{
    const JsonSerializable();
}

class JSONField {
  //Specify the parse field name
  final String? name;

  //Whether to participate in toJson
  final bool? serialize;
  
  //Whether to participate in fromMap
  final bool? deserialize;

  const JSONField({this.name, this.serialize, this.deserialize});
}

四臊旭、使用

1.單實(shí)體解析

直接調(diào)用實(shí)體類(lèi)對(duì)應(yīng)的``fromJson```方法即可將 Json 數(shù)據(jù)解析為實(shí)體對(duì)象落恼。

UserEntity? user;  
String userData = """
     {
        "id":"1",
        "name":"qi",
        "age":22
     }
    """;

user = UserEntity.fromJson(jsonDecode(userData));

調(diào)用生成的JsonConvert去解析,使用convertasT离熏、fromJsonAsT都能得到結(jié)果

user = jsonConvert.convert<UserEntity>(jsonDecode(userData));

user = jsonConvert.asT<UserEntity>(jsonDecode(userData));

user = JsonConvert.fromJsonAsT<UserEntity>(jsonDecode(userData));

2.List解析

解析 Json List 數(shù)據(jù)則需要調(diào)用 JsonConvert 的對(duì)應(yīng)方法進(jìn)行解析佳谦,除了使用上面的 convertasT滋戳、fromJsonAsT 外钻蔑,還可以使用 convertListconvertListNotNull

List<UserEntity>? users;
List<UserEntity?>? userNulls;

users = jsonConvert.convert<List<UserEntity>>(jsonDecode(userData));

users = jsonConvert.asT<List<UserEntity>>(jsonDecode(userData));

users = JsonConvert.fromJsonAsT<List<UserEntity>>(jsonDecode(userData));

users = jsonConvert.convertListNotNull<UserEntity>(jsonDecode(userData));

userNulls = jsonConvert.convertList<UserEntity>(jsonDecode(userData));

convertList 奸鸯、convertListNotNullconvert 咪笑、asTfromJsonAsT 的區(qū)別在于前者的泛型為 List Item元素的泛型類(lèi)型娄涩,后者則直接為對(duì)應(yīng) List 的類(lèi)型窗怒。如上面 convertListconvertListNotNull的泛型直接為UserEntity , 而 convert 蓄拣、asT扬虚、fromJsonAsT 的泛型為List<UserEntity>

3.JSONField的使用

自定義字段名

處理Json數(shù)據(jù)字段和實(shí)體屬性字段不一致的問(wèn)題球恤,如后臺(tái)返回Json命名不規(guī)范這種情況辜昵。可以用JSONField
自定義字段映射碎捺。如后臺(tái)返回 AGE就可以如下使用路鹰,映射成age。加完之后照樣需要執(zhí)行Alt+J

  @JSONField(name: "AGE")
  String? age;
忽略字段

JSONField 還有兩個(gè)字段 serialize 收厨、deserialize 用于序列化和反序列化時(shí)忽略某個(gè)字段晋柱。

五、優(yōu)化

后臺(tái)返回的數(shù)據(jù)一般是經(jīng)過(guò)一層包裝

{
  "code": 200,
  "message": "success",
  "data":{
    "id": "1",
    "name": "qi1",
    "age": 18
  }
}

而重新用插件生成會(huì)生成如下代碼:

@JsonSerializable()
class ApiResponseEntity {

    int? code;
    String? message;
    ApiResponseData? data;
  
  ApiResponseEntity();

  factory ApiResponseEntity.fromJson(Map<String, dynamic> json) => $ApiResponseEntityFromJson(json);

  Map<String, dynamic> toJson() => $ApiResponseEntityToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}

@JsonSerializable()
class ApiResponseData {

    String? id;
    String? name;
    int? age;
  
  ApiResponseData();

  factory ApiResponseData.fromJson(Map<String, dynamic> json) => $ApiResponseDataFromJson(json);

  Map<String, dynamic> toJson() => $ApiResponseDataToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}

要死這樣诵叁,每一個(gè)接口的都有一個(gè)ResponseEntity雁竞,使用起來(lái)不便于統(tǒng)一封裝。
所以我們可以把ApiResponseData換成 dynamic拧额,文件底部的ApiResponseData信息也全部刪除碑诉,再執(zhí)行Alt+J,這樣就會(huì)自動(dòng)清理掉整理json_convert_content.dartapi_response_entity.g.dart中的ApiResponseData痕跡侥锦。再把dynamic替換成T,并且去除頂部的@JsonSerializable()进栽,避免下次執(zhí)行Alt+J,替換掉自己的自定義恭垦。

@JsonSerializable()
class ApiResponseEntity<T> {
  late int code;
  late String message;
  late T data;

  ApiResponseEntity();

  factory ApiResponseEntity.fromJson(Map<String, dynamic> json) =>
      $ApiResponseEntityFromJson<T>(json);

  Map<String, dynamic> toJson() => $ApiResponseEntityToJson(this);

  @override
  String toString() {
    return jsonEncode(this);
  }
}
ApiResponseEntity<T> $ApiResponseEntityFromJson<T>(Map<String, dynamic> json) {
  final ApiResponseEntity<T> apiResponseEntity = ApiResponseEntity<T>();
  final int? code = jsonConvert.convert<int>(json['code']);
  if (code != null) {
    apiResponseEntity.code = code;
  }
  final String? message = jsonConvert.convert<String>(json['message']);
  if (message != null) {
    apiResponseEntity.message = message;
  }
  final T data = jsonConvert.convert<dynamic>(json['data']);
  if (data != null) {
    apiResponseEntity.data = data;
  }
  return apiResponseEntity;
}

Map<String, dynamic> $ApiResponseEntityToJson(ApiResponseEntity entity) {
  final Map<String, dynamic> data = <String, dynamic>{};
  data['code'] = entity.code;
  data['message'] = entity.message;
  data['data'] = entity.data;
  return data;
}

并且把api_response_entity.g.dart移除generated目錄快毛,因?yàn)槟莻€(gè)目錄會(huì)自動(dòng)刪除無(wú)用的文件格嗅。可以和api_reponse_entity.dart單獨(dú)存放在一個(gè)文件夾當(dāng)中唠帝。

優(yōu)化后使用

第一次發(fā)現(xiàn)屯掖,reponse的data是null。因?yàn)樾碌牟寮?asT方法沒(méi)有去調(diào)用fromJsonAsT襟衰,這個(gè)需要我們自加上贴铜,否則會(huì)失敗。

if (_convertFuncMap.containsKey(type)) {
  return _convertFuncMap[type]!(value) as T;
} else {
  return fromJsonAsT<T>(value);
  // throw UnimplementedError('$type unimplemented');
}
  //單實(shí)體
  String responseData1 = """
    {
      "code": 200,
      "message": "success",
      "data":{
        "id": 1,
        "name": "qi1",
        "age": 21
      }
    }
    """;

  //List
  String responseData2 = """
    {
      "code": 200,
      "message": "success",
      "data":[
        {
          "id": 1,
          "name": "qi1",
          "age": 21
        },{
          "id": 2,
          "name": "qi2",
          "age": 22
        }
      ]
    }
    """;

//基礎(chǔ)數(shù)據(jù)類(lèi)型
  String responseData3 = """
    {
      "code": 200,
      "message": "success",
      "data": 18
    }
    """;

  _apiResponseDecode() {
    setState(() {
      response1 = ApiResponseEntity.fromJson(jsonDecode(responseData1));
      response2 = ApiResponseEntity.fromJson(jsonDecode(responseData2));
      response3 = ApiResponseEntity.fromJson(jsonDecode(responseData3));
    });
  }

  _getApiResponseContent() {
    return response1.toString() +
        "\n" +
        response2.toString() +
        "\n" +
        response3.toString();
  }

參考鏈接:https://juejin.cn/post/7043721908801503269

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瀑晒,一起剝皮案震驚了整個(gè)濱河市绍坝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瑰妄,老刑警劉巖陷嘴,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異间坐,居然都是意外死亡灾挨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)竹宋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)劳澄,“玉大人,你說(shuō)我怎么就攤上這事蜈七∶氚危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵飒硅,是天一觀(guān)的道長(zhǎng)砂缩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)三娩,這世上最難降的妖魔是什么庵芭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮雀监,結(jié)果婚禮上双吆,老公的妹妹穿的比我還像新娘。我一直安慰自己会前,他們只是感情好好乐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瓦宜,像睡著了一般蔚万。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上临庇,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天笛坦,我揣著相機(jī)與錄音区转,去河邊找鬼。 笑死版扩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侄泽。 我是一名探鬼主播礁芦,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼悼尾!你這毒婦竟也來(lái)了柿扣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闺魏,失蹤者是張志新(化名)和其女友劉穎未状,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體析桥,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡司草,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泡仗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埋虹。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖娩怎,靈堂內(nèi)的尸體忽然破棺而出搔课,到底是詐尸還是另有隱情,我是刑警寧澤截亦,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布爬泥,位于F島的核電站,受9級(jí)特大地震影響崩瓤,放射性物質(zhì)發(fā)生泄漏袍啡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一谷遂、第九天 我趴在偏房一處隱蔽的房頂上張望葬馋。 院中可真熱鬧,春花似錦肾扰、人聲如沸畴嘶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窗悯。三九已至,卻和暖如春偷拔,著一層夾襖步出監(jiān)牢的瞬間蒋院,已是汗流浹背亏钩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欺旧,地道東北人姑丑。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像辞友,于是被迫代替她去往敵國(guó)和親栅哀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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