Gson使用指南

泛型:

  1. https://juejin.im/post/5b614848e51d45355d51f792#heading-7
  2. https://www.jianshu.com/p/5179ede4c4cf

Gson使用指南


  1. 概览
  2. Gson的目标
  3. Gson性能和可扩展性
  4. Gson用户
  5. 使用Gson(这里指的是android)
    • 在Gradle/Android中使用Gson
    • 例子
    • 对象实例
    • 对象细节
    • 嵌套类(包括内部类)
    • 数组示例
    • 集合示例
    • 序列化和反序列化泛型类型
    • 任意类型对象集合的序列化和反序列化
    • 内置的序列化器和反序列化器
    • 自定义序列化和反序列化
      • 编写序列化程序
      • 编写反序列化程序
    • 编写实例创建器
    • 紧凑型 vs JSON的漂亮输出格式
    • 空对象支持
    • 版本控制支持
    • 从序列化和反序列化中排除字段
    • JSON字段命名支持
    • 在自定义序列化和反序列化之间共享状态
  6. 设计Gson的问题
  7. Gson的未来改进

概览


Gson是一个Java库,可用于将Java对象转换为其JSON表示。还可以用于将JSON字符串转换为等效的Java对象。
Gson可以处理任意Java对象,包括没有在代码中预设的对象。

Gson的目标


  • 提供易于使用的机制,如toString()构造函数(工厂方法),将Java转换为JSON,反之亦然
  • 允许将预先存在的不可修改对象转换为JSON或从JSON转换
  • 允许对象的自定义表示
  • 支持任意复杂的对象
  • 生成紧凑且可读的JSON输出

Gson性能和可扩展性


Gson用户


Gson的使用


要使用的主要类是Gson,可以通过调用创建的new Gson()以及GsonBuilder类用于创建具有各种设置(如版本控制等)的Gson实例。
在调用Json操作时,Gson实例不维护任何状态。因此,您可以重用相同的Gson对象自由地为多个Json进行序列化和反序列化操作。

在Gradle/Android中使用Gson


dependencies {
    implementation 'com.google.code.gson:gson:2.8.5'
}

具体版本请参照网站
https://github.com/google/gson

例子:


// 序列化
Gson gson = new Gson();
gson.toJson(1);            // ==> 1
gson.toJson("abcd");       // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values);       // ==> [1]

// 反序列化
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String[] anotherStr = gson.fromJson("[\"abc\"]", String[].class);

对象实例


class BagOfPrimitives{
    private int value1 = 1;
    private String value2 = "abc";
    private transient int value3 = 3;
    BagOfPrimitives{
        // 无参构造
    }
}

// 序列化
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json 内容是 {"value1":1,"value2":"abc"}

// 反序列化
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2 是一个像 obj 的对象

对象细节:

  • 最好使用private修饰字段
  • 无需使用任何注释来指示要进行序列化和反序列化的字段。默认情况下当前类(包括所有从父类继承)的所有字段都会被包含
  • 如果一个字段被transient修饰了,则在序列化和反序列化的过程会被忽略
  • Gson实现会正确处理空值
    • 当序列化时,一个为空的字段将被忽略
    • 当反序列化时,在反序列化时,JSON中缺少的条目导致将对象中的相应字段设置为其默认值:对象类型为null,数字类型为零,布尔值为false。
  • 如果字段是合成的,则会被忽略,并且不包含在JSON序列化或反序列化中。
  • 内部类,匿名类和本地类的字段会被忽略并且不被包含在序列化和反序列化中

嵌套类(包括内部类)


Gson能够地序列化和反序列化静态嵌套类。但是单纯的内部类不能被Gson序列化和反序列化,因为再反序列的时候需要一个实例的引用去获得实际的类型,你可以通过使用静态内部类或为其提供自定义InstanceCreator来解决此问题,如:

public class A { 
  public String a; 

  class B { 

    public String b; 

    public B() {
      // B的无参构造
    }
  } 
}

注意:上述的类B不能被Gson序列化。
可以通过定义B为静态内部类,来让Gson实现序列化和反序列化,如:

public class A { 
  public String a; 

  static class B { 

    public String b; 

    public B() {
      // B的无参构造
    }
  } 
}

也可以通过一个自定义的creator来解决,如:

public class InstanceCreatorForB implements InstanceCreator<A.B> {
  private final A a;
  public InstanceCreatorForB(A a)  {
    this.a = a;
  }
  public A.B createInstance(Type type) {
    return a.new B();
  }
}

上述的方法不被推荐。

数组示例


Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};

// Serialization
gson.toJson(ints);     // ==> [1,2,3,4,5]
gson.toJson(strings);  // ==> ["abc", "def", "ghi"]

// Deserialization
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class); 
// ==> ints2 will be same as ints

集合示例


Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);

// Serialization
String json = gson.toJson(ints);  // ==> json is [1,2,3,4,5]

// Deserialization
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 is same as ints

这种写法不被推荐,但在JAVA中无法解决这个问题。
集合的限制:
Gson可以序列化任意对象的集合,但不能从中反序列化,因为用户无法指示结果对象的类型。相反,在反序列化时,Collection必须是特定的泛型类型。

序列化和反序列化泛型类型


当你调用toJson(obj),Gson会调用obj.getClass()来获取有关要序列的字段的信息。同样,你还可以传递Myclass.class参数给fromJson(json, MyClass.class)。如果对象是非泛型类型,则此方法正常。但是,如果对象是泛型类型,则由于Java类型擦除而丢失通用类型信息。如:

class Foo<T> {
  T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // 不能被正确的序列化

gson.fromJson(json, foo.getClass()); // 不能被正确的反序列化

上面的例子代码不能将Bar这个类型正确的反序列化,因为Gson调用list.getClass()去获取class的信息,但返回的是Foo.class。这意味着Gson无法知道这是一个Foo<Bar>类型的对象,而不仅仅是普通的Foo。您可以通过为泛型类型指定正确的参数化类型来解决此问题。您可以使用TypeToken该类来完成此操作。如:

Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);

gson.fromJson(json, fooType);

fooType实际上定义了一个匿名本地内部类,其中包含一个getType()返回完全参数化类型的方法。

任意类型对象集合的序列化和反序列化


有时候,你会处理这样有多种数据类型的JSON数组,如:

['hello',5,{name:'GREETINGS',source:'guest'}]

这相当于一个这样的Collection:

class Event {
  private String name;
  private String source;
  private Event(String name, String source) {
    this.name = name;
    this.source = source;
  }
}
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));

你可以直接调用toJson(collection)即可得到上面的JSON数组字符串。但是反序列化fromJson(json, Collection.class)则无法成功,因为Gson不知道如何去匹配数据类型。Gson需要你去提供集合类型的通用版本个体fromJson().这时候你有三个选择:

  1. 使用Gson的解析器API(低级流解析器或DOM解析器JsonParser)来解析数组元素,然后Gson.fromJson()在每个数组元素上使用。这是首选方法。这是一个演示如何执行此操作的示例
  2. 注册一个类型适配器Collection.class,查看每个数组成员并将它们映射到适当的对象。这种方法的缺点是它会影响Gson中其他集合类型的反序列化。
  3. 注册一个类型的适配器MyCollectionMemberType,并使用fromJson()与Collection<MyCollectionMemberType>。
    仅当数组显示为顶级元素或者您可以更改将集合保持为类型的字段类型时,才可用Collection<MyCollectionMemberType>此方法。

内置的序列化和反序列化


Gson有常用类的内置序列化和反序列化器,如:
JodaTime

自定义序列化和反序列化


有时默认表示不是您想要的。处理一些库类(如DateTime等)时经常会出现这种情况。Gson允许您注册自己的自定义序列化程序和反序列化器。这是通过定义两部分来完成的:

  • Json Serializers:必须,为对象定义自定义序列化
  • Json Deserializers:必须,为类型定义自定义反序列化
  • Instance Creators: 可选,如果无参构造方法或者Json Deserializers被注册了的话,则不需要

例子:

GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter());
gson.registerTypeAdapter(MyType.class, new MySerializer());
gson.registerTypeAdapter(MyType.class, new MyDeserializer());
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());

registerTypeAdapter的调用是检查是否type adapter实现了其中之一的接口,并且将所有接口都注册。

实现Serializer

private class DateTimeSerializer implements JsonSerializer<DateTime> {
  public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
    return new JsonPrimitive(src.toString());
  }
}

Gson 在序列化期间遇到DateTime对象时调用serialize()。

实现Deserializers

private class DateTimeDeserializer implements JsonDeserializer<DateTime> {
  public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException {
    return new DateTime(json.getAsJsonPrimitive().getAsString());
  }
}

Gson 在反序列化期间遇到DateTime对象时调用deserialize。

自定义序列化和反序列化实例:

// Custom Serialize
public class DogSerializer implements JsonSerializer<dog> {
    @Override
    public JsonElement serialize(Dog src, Type typeOfSrc, JsonSerializationContext context) {
        // This method gets involved whenever the parser encounters the Dog
        // object (for which this serializer is registered)
        JsonObject object = new JsonObject();
        String name = src.getName().replaceAll(" ", "_");
        object.addProperty("name", name);
        // we create the json object for the dog and send it back to the
        // Gson serializer
        return object;
    }
 
    public static void main(String[] args) {
        Animal<Dog> animal = new Animll<Dog>();
        Dog dog = new Dog("I am a dog");
        animal.setAnimal(dog);
        // Create the GsonBuilder and register a serializer for the Dog class.
        // Whenever the Dog class is encountered Gson calls the DogSerializer
        // we set pretty printing own to format the json
        Gson gson = new GsonBuilder().registerTypeAdapter(Dog.class, new DogSerializer()).setPrettyPrinting().create();
        // Since Animal contains generic type create the type using TypeToken
        // class.
        Type animalType = new TypeToken<Animal<Dog>>() {
        }.getType();
        System.out.println(gson.toJson(animal, animalType));
    }
}

// The Animal class
public class Animal<t> {
 
    public T animal;
 
    public void setAnimal(T animal) {
        this.animal = animal;
    }
 
    public T get() {
        return animal;
    }
 
}

// The Dog class
public class Dog {
    private String name;
 
    public Dog(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
}

// Custom Deserialize
public class DogDeserialiser implements JsonDeserializer<Dog> {
    @Override
    public Dog deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        String name = json.getAsJsonObject().get("name").getAsString();
        name = name.replace(" ", "_");
        Dog dog = new Dog(name);
 
        return dog;
    }
 
    public static void main(String[] args) {
        String json = "{\"animal\":{\"name\":\"I am a dog\"}}";
        Gson gson = new GsonBuilder().registerTypeAdapter(Dog.class, new DogDeserialiser()).create();
        Type animalType = new TypeToken<Animal<Dog>>() {
        }.getType();
        Animal<Dog> animal = gson.fromJson(json, animalType);
        System.out.println(animal.get().getName());
    }
 
}

编写实例创建器


当进行对象的反序列化时,Gson创建需要一个类的默认实例。一个编写优美的类意味着拥有序列化和反序列化所需要的无参构造方法(可以是public也可以是private)。
通常如果一个类没有无参构造方法就可以使用实例创建器。如:

private class MoneyInstanceCreator implements InstanceCreator<Money> {
  public Money createInstance(Type type) {
    return new Money("1000000", CurrencyCode.USD);
  }
}

这里的类型可以是对应的泛型。
有时,您尝试实例化的类型是参数化类型。通常,这不是问题,因为实际的实例是原始类型。这是一个例子:

class MyList<T> extends ArrayList<T> {
}

class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
    @SuppressWarnings("unchecked")
  public MyList<?> createInstance(Type type) {
    // No need to use a parameterized list since the actual instance will have the raw type anyway.
    return new MyList();
  }
}

但是,有时您需要根据实际参数化类型创建实例。在这种情况下,您可以使用传递给createInstance方法的type参数。这是一个例子:

public class Id<T> {
  private final Class<T> classOfId;
  private final long value;
  public Id(Class<T> classOfId, long value) {
    this.classOfId = classOfId;
    this.value = value;
  }
}

class IdInstanceCreator implements InstanceCreator<Id<?>> {
  public Id<?> createInstance(Type type) {
    Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
    Type idType = typeParameters[0]; // Id has only one parameterized type T
    return Id.get((Class)idType, 0L);
  }
}

在上面的示例中,如果没有实际传入参数化类型的实际类型,则无法创建Id类的实例。我们通过使用传递的方法参数type来解决这个问题。在这种情况下,type对象是Java参数化类型表示Id<Foo>实际实例应绑定到的位置Id<Foo>。由于Id类只有一个参数化类型参数,T我们使用返回的类型数组的第0个元素,在这种情况下getActualTypeArgument()它将保存Foo.class。

紧凑型 vs JSON的漂亮输出格式


Gson提供的默认JSON输出是紧凑的JSON格式。这意味着输出JSON结构中不会有任何空格。因此,JSON输出中的字段名称及其值,对象字段和数组内的对象之间不会有空格。同样,输出中将忽略“null”字段。

如果要使用“Pretty Print”功能,则必须Gson使用“ 配置”来配置实例GsonBuilder。Gson没有通过的公共API公开JsonFormatter,所以客户端无法配置默认print settings/margins的JSON输出。目前,我们只提供JsonPrintFormatter默认行长度为80个字符,2个字符缩进和4个字符右边距。

以下是一个示例,说明如何配置Gson实例以使用默认值JsonPrintFormatter而不是JsonCompactFormatter:

Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);

Null 对象支持


Gson的默认实现中null值是被忽略的。然而,当你在使用Gson时想为这里字段添加一个默认值时,你可以这样配置Gson,如:

Gson gson = new GsonBuilder().serializeNulls().create();

注意:这样配置后,当Gson在序列化null时,会在JsonElement中增加一个JsonNull元素。因此,这个值可以被用在自定义序列化和反序列化中。如:

public class Foo {
  private final String s;
  private final int i;

  public Foo() {
    this(null, 5);
  }

  public Foo(String s, int i) {
    this.s = s;
    this.i = i;
  }
}

Gson gson = new GsonBuilder().serializeNulls().create();
Foo foo = new Foo();
String json = gson.toJson(foo);
System.out.println(json);

json = gson.toJson(null);
System.out.println(json);

输出为:

{"s":null,"i":5}
null

版本支持


使用@Since注释可以维护同一对象的多个版本。此批注可用于类,字段以及将来的发行版中的方法。要利用此功能,必须将Gson实例配置为忽略任何大于某个版本号的字段/对象。如果没有在Gson实例上设置任何版本,则无论版本如何,它都将序列化和反序列化所有字段和类。如:

public class VersionedClass {
  @Since(1.1) private final String newerField;
  @Since(1.0) private final String newField;
  private final String field;

  public VersionedClass() {
    this.newerField = "newer";
    this.newField = "new";
    this.field = "old";
  }
}

VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput);
System.out.println();

gson = new Gson();
jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput);

输出为:

{"newField":"new","field":"old"}

{"newerField":"newer","newField":"new","field":"old"}

从序列化和反序列化中排除字段


Gson提供大量的排除顶级类,字段和字段类型的机制。下面是灵活的机制,允许字段和类排除。如果以下机制都不能满足您的需求,那么您始终可以使用自定义序列化程序和反序列化程序。

  1. Java修饰符排除

默认情况下,如果将字段标记为transient,则将排除该字段。同样,如果某个字段被标记为static默认情况下将被排除。如果要包含一些transient 字段,则可以执行以下操作:

import java.lang.reflect.Modifier;
Gson gson = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.STATIC)
    .create();

注意:您可以为excludeFieldsWithModifiers方法提供任意数量的Modifier常量。如:

Gson gson = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
    .create();
  1. Gson的@Expose
    可以使用@Expose注解去排除你不想被Gson序列化和反序列化的字段。但Gson实例必须通过new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().来创建。这样Gson就会排除掉注解注释的字段。
  2. 如果以上的机制都不适合你,可以编写你自己的排除策略,并加入到Gson中。参考ExclusionStrategy文档。以下为例子:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Foo {
  // Field tag only annotation
}

public class SampleObjectForTest {
  @Foo private final int annotatedField;
  private final String stringField;
  private final long longField;
  private final Class<?> clazzField;

  public SampleObjectForTest() {
    annotatedField = 5;
    stringField = "someDefaultValue";
    longField = 1234;
  }
}

public class MyExclusionStrategy implements ExclusionStrategy {
  private final Class<?> typeToSkip;

  private MyExclusionStrategy(Class<?> typeToSkip) {
    this.typeToSkip = typeToSkip;
  }

  public boolean shouldSkipClass(Class<?> clazz) {
    return (clazz == typeToSkip);
  }

  public boolean shouldSkipField(FieldAttributes f) {
    return f.getAnnotation(Foo.class) != null;
  }
}

public static void main(String[] args) {
  Gson gson = new GsonBuilder()
      .setExclusionStrategies(new MyExclusionStrategy(String.class))
      .serializeNulls()
      .create();
  SampleObjectForTest src = new SampleObjectForTest();
  String json = gson.toJson(src);
  System.out.println(json);
}

输出为:

{"longField":1234}

JSON字段命名支持


Gson支持一些预定义的字段命名策略,以将标准Java字段名称(即以小写字母开头的驼峰名称sampleFieldNameInJava)转换为Json字段名称(即sample_field_name_in_java或SampleFieldNameInJava)。有关预定义命名策略的信息,请参阅FieldNamingPolicy类。如:

private class SomeObject {
  @SerializedName("custom_naming") private final String someField;
  private final String someOtherField;

  public SomeObject(String a, String b) {
    this.someField = a;
    this.someOtherField = b;
  }
}

SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);

输出为:

{"custom_naming":"first","SomeOtherField":"second"}

自定义序列化和反序列化共享状态


有时您需要在自定义序列化器/反序列化器之间共享状态)。可以使用以下三种策略来完成此任务:

  1. 在静态字段中存储共享状态
  2. 将序列化器/反序列化器声明为父类型的内部类,并使用父类型的实例字段来存储共享状态
  3. 使用Java ThreadLocal
    1和2不是线程安全选项,但3是。


除了Gson的对象模型和数据绑定之外,您还可以使用Gson读取和写入。您还可以组合流和对象模型访问,以获得两种方法中的最佳方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259