Skip to content

1.存储 1 AVObject

jwfing edited this page Jun 28, 2018 · 4 revisions

存储服务概述

数据存储(LeanStorage)是 LeanCloud 提供的核心功能之一,它的使用方法与传统的关系型数据库有诸多不同。下面我们将其与传统数据库的使用方法进行对比,让大家有一个初步了解。 下面这条 SQL 语句在绝大数的关系型数据库都可以执行,其结果是在 Todo 表里增加一条新数据:

INSERT INTO Todo (title, content) VALUES ('工程师周会', '每周工程师会议,周一下午 2 点')

使用传统的关系型数据库作为应用的数据源几乎无法避免以下步骤:

  • 插入数据之前一定要先创建一个表结构,并且随着之后需求的变化,开发者需要不停地修改数据库的表结构,维护表数据。
  • 每次插入数据的时候,客户端都需要连接数据库来执行数据的增删改查(CRUD)操作。

使用 LeanStorage,实现代码如下:

AVObject todo = new AVObject("Todo");
todo.put("title", "工程师周会");
todo.put("content", "每周工程师会议,周一下午2点");
todo.saveInBackground().subscribe(new Observer<AVObject>() {
      public void onSubscribe(Disposable disposable) {}

      public void onNext(AVObject avObject) {
        System.out.println("succeed to save Object.");
      }

      public void onError(Throwable throwable) {}

      public void onComplete() {}
    });

使用 LeanStorage 的特点在于:

  • 不需要单独维护表结构。例如,为上面的 Todo 表新增一个 location 字段,用来表示日程安排的地点,那么刚才的代码只需做如下变动:
AVObject todo = new AVObject("Todo");
todo.put("title", "工程师周会");
todo.put("content", "每周工程师会议,周一下午2点");
todo.put("location", "会议室");// 只要添加这一行代码,服务端就会自动添加这个字段
todo.saveInBackground().subscribe(new Observer<AVObject>() {
      public void onSubscribe(Disposable disposable) {}

      public void onNext(AVObject avObject) {
        System.out.println("succeed to save Object.");
      }

      public void onError(Throwable throwable) {}

      public void onComplete() {}
    });
  • 数据可以随用随加,这是一种无模式化(Schema Free)的存储方式。
  • 所有对数据的操作请求都通过 HTTPS 访问标准的 REST API 来实现。
  • 我们为各个平台或者语言开发的 SDK 在底层都是调用统一的 REST API,并提供完整的接口对数据进行增删改查。

LeanStorage 在结构化数据存储方面,与 DB 的区别在于:

  • Schema Free/Not free 的差异;
  • 数据接口上,LeanStorage 是面向对象的(数据操作接口都是基于 Object 的),开放的(所有移动端都可以直接访问),DB 是面向结构的,封闭的(一般在 Server 内部访问);
  • 数据之间关联的方式,DB 是主键外键模型,LeanStorage 则有自己的关系模型(Pointer、Relation 等);

LeanStorage 支持两种存储类型:对象(AVObject)和文件(AVFile),下面我们来逐一具体说明一下他们的用法。

数据类型

AVObject 是 LeanStorage 对复杂对象的封装,每个 AVObject 包含若干属性值对,也称键值对(key-value)。属性的值是与 JSON 格式兼容的数据。通过 REST API 保存对象需要将对象的数据通过 JSON 来编码。这个数据是无模式化的(Schema Free),这意味着你不需要提前标注每个对象上有哪些 key,你只需要随意设置 key-value 对就可以,云端会保存它。 AVObject 支持以下数据类型:

简单类型

  • 字符(Char)
  • 字符串(String)
  • 布尔值(Boolean)
  • 数字(Integer,Double,Long,Float)
  • 日期(Date)。注意,时间类型在云端将会以 UTC 时间格式存储,但是客户端在读取之后会做转化成本地时间。
  • 二进制数据(byte[])。注意:我们不推荐AVObject 中使用 byte[] 来储存大块的二进制数据,比如图片或整个文件。每个 AVObject 的大小都不应超过 128 KB。如果需要储存更多的数据,建议使用 AVFile

复合类型

  • Object,可以把任意的 Java Object 当成属性值进行存储。
  • AVGeoPoint,地理位置信息。
  • AVObject。可以把一个 AVObject 实例当成另一个 AVObject 的属性值进行存储。
  • ArrayList。可以把对象的数组当成属性值进行存储。
  • HashMap。可以把一个属性值对的 Map 当成属性值进行存储。

注意:HashMap 和 ArrayList 支持嵌套,这样在一个 AVObject 中就可以使用它们来储存更多的结构化数据。

通过下面的示例,我们可以更好地了解数据类型:

boolean bool = true;
int number = 2015;
String string = number + " 年度音乐排行";
Date date = new Date();

byte[] data = "短篇小说".getBytes();
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(number);
arrayList.add(string);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("数字", number);
hashMap.put("字符串", string);

AVObject object = new AVObject("DataTypes");
object.put("testBoolean", bool);
object.put("testInteger", number);
object.put("testDate", date);
object.put("testData", data);
object.put("testArrayList", arrayList);
object.put("testHashMap", hashMap);
object.save();

构建对象

构建一个 AVObject 可以使用如下方式:

// 构造方法传入的参数,对应的就是控制台中的 Class Name
AVObject todo = new AVObject("Todo");

每个 AVObject 必须有一个 Class 类名称,这样云端才知道它的数据归属于哪张数据表。

Class 类名称(ClassName)必须以字母开头,只能包含字母、数字和下划线。

保存对象

生成了 AVObject 实例之后,通过调用 save(同步)或者 saveInBackground(异步)函数可以保存这个对象。 假如我们要保存一个 TodoFolder,它可以包含多个 Todo,类似于给行程按文件夹的方式分组。我们并不需要提前去后台创建这个名为 TodoFolder 的 Class 类,而仅需要执行如下代码,云端就会自动创建这个类:

AVObject todoFolder = new AVObject("TodoFolder");// 构建对象
todoFolder.put("name", "工作");// 设置名称
todoFolder.put("priority", 1);// 设置优先级
todoFolder.save();// 保存到服务端

创建完成后,打开 控制台 > 存储,点开 TodoFolder 类,就可以看到刚才添加的数据。除了 name、priority(优先级)之外,其他字段都是数据表的内置属性。

内置属性 类型 描述
objectId "String" 该对象唯一的 Id 标识
ACL "ACL" 该对象的权限控制,实际上是一个 JSON 对象,控制台做了展现优化。
createdAt Date 该对象被创建的 UTC 时间
updatedAt Date 该对象最后一次被修改的时间
属性名
也叫键或 key,必须是由字母、数字或下划线组成的字符串。
自定义的属性名,**不能以双下划线 `__` 开头,也不能与以下系统保留字段和内置属性重名(不区分大小写)**。
ACL、className、createdAt、objectId、updatedAt
属性值
可以是字符串、数字、布尔值、数组或字典。

为提高代码的可读性和可维护性,建议使用驼峰式命名法(CamelCase)为类和属性来取名。类,采用大驼峰法,如 CustomData。属性,采用小驼峰法,如 imageUrl

保存选项

AVObject 对象在保存时可以设置选项来快捷完成关联操作,可用的选项属性有:

选项 类型 适用操作 说明
AVSaveOption.fetchWhenSave Boolean create
update
对象成功保存后,自动返回其在云端的最新值。create 操作返回该对象的所有属性,update 操作只返回被更新了的属性的最新值。用法请参考 更新计数器
AVSaveOption.matchQuery AVQuery update 当 query 中的条件满足后,对象才能被更新,否则系统会放弃更新,并返回错误码 305。

通过 query 指定的条件仅对已存在的对象生效,如果用于保存新对象,则不生效。

开发者原本可以通过 AVQueryAVObject 分两步来实现这样的逻辑,但如此一来无法保证操作的原子性从而导致并发问题。该选项可以用来判断多用户更新同一对象数据时可能引发的冲突。用法请参考 按条件更新对象

使用 CQL 语法保存对象

获取对象

每个被成功保存在云端的对象会有一个唯一的 Id 标识 objectId,因此获取对象的最基本的方法就是根据 objectId 来查询:

AVQuery<AVObject> avQuery = new AVQuery<>("Todo");
avQuery.getInBackground("558e20cbe4b060308e3eb36c").subscribe(new Observer<AVObject>() {
  public void onSubscribe(Disposable disposable) {
  }
  public void onNext(AVObject o) {
    System.out.println(o.toString());
  }
  public void onError(Throwable throwable) {
    throwable.printStackTrace();
  }
  public void onComplete() {
  }
});

除了使用 AVQuery,还可以采用在本地构建一个 AVObject 的方式,通过接口和 objectId 把数据从云端拉取到本地:

// 第一参数是 className,第二个参数是 objectId
AVObject todo = AVObject.createWithoutData("Todo", "558e20cbe4b060308e3eb36c");
todo.fetchInBackground().subscribe(new Observer<AVObject>() {
  public void onSubscribe(Disposable disposable) {
  }
  public void onNext(AVObject o) {
    System.out.println(o.toString());
  }
  public void onError(Throwable throwable) {
    throwable.printStackTrace();
  }
  public void onComplete() {
  }
});

获取 objectId

每一次对象存储成功之后,云端都会返回 objectId,它是一个全局唯一的属性。

final AVObject todo = new AVObject("Todo");
todo.put("title", "工程师周会");
todo.put("content", "每周工程师会议,周一下午2点");
todo.put("location", "会议室");// 只要添加这一行代码,服务端就会自动添加这个字段
todo.saveInBackground().subscribe(new Observer<AVObject>() {
      public void onSubscribe(Disposable disposable) {}

      public void onNext(AVObject avObject) {
        System.out.println("succeed to save Object. objectId:" + avObject.getObjectId());
      }

      public void onError(Throwable throwable) {}

      public void onComplete() {}
    });

访问对象属性

访问 Todo 的属性的方式为:

AVQuery<AVObject> avQuery = new AVQuery<>("Todo");
avQuery.getInBackground("558e20cbe4b060308e3eb36c").subscribe(new Observer<AVObject>() {
  public void onSubscribe(Disposable disposable) {}

  public void onNext(AVObject avObject) {
    int priority = avObject.getInt("priority");
    String location = avObject.getString("location");
    String title = avObject.getString("title");
    String content = avObject.getString("content");

    // 获取三个特殊属性
    String objectId = avObject.getObjectId();
    Date updatedAt = avObject.getUpdatedAt();
    Date createdAt = avObject.getCreatedAt();
  }

  public void onError(Throwable throwable) {}

  public void onComplete() {}
});

默认属性

默认属性是所有对象都会拥有的属性,它包括 objectIdcreatedAtupdatedAt

  • createdAt,对象第一次保存到云端的时间戳。该时间一旦被云端创建,在之后的操作中就不会被修改。
  • updatedAt,对象最后一次被修改(或最近一次被更新)的时间。

注:应用控制台对 createdAt 和 updatedAt 做了在展示优化,它们会依据用户操作系统时区而显示为本地时间;客户端 SDK 获取到这些时间后也会将其转换为本地时间;而通过 REST API 获取到的则是原始的 UTC 时间,开发者可能需要根据情况做相应的时区转换。

同步对象

多终端共享一个数据时,为了确保当前客户端拿到的对象数据是最新的,可以调用刷新接口来确保本地数据与云端的同步:

// 假如已知了 objectId 可以用如下的方式构建一个 AVObject
AVObject anotherTodo = AVObject.createWithoutData("Todo", "5656e37660b2febec4b35ed7");
// 然后调用刷新的方法,将数据从服务端拉到本地
anotherTodo.fetchInBackground().subscribe(new Observable<AVObject>() {
  public void onSubscribe(Disposable disposable) {}

  public void onNext(AVObject avObject) {
    // 调用 fetchInBackground 和 refreshInBackground 效果是一样的。
  }

  public void onError(Throwable throwable) {}

  public void onComplete() {}
});

在更新对象操作后,对象本地的 updatedAt 字段(最后更新时间)会被刷新,直到下一次 save 操作,updatedAt 的最新值才会被同步到云端,这样做是为了减少网络流量传输。

同步指定属性

目前 Todo 这个类已有四个自定义属性:prioritycontentlocationtitle。为了节省流量,现在只想刷新 prioritylocation可以使用如下方式:

// 假如已知了 objectId 可以用如下的方式构建一个 AVObject
AVObject anotherTodo = AVObject.createWithoutData("Todo", "5656e37660b2febec4b35ed7");
String keys = "priority,location";// 指定刷新的 key 字符串
anotherTodo.fetchInBackground(keys).subscribe(new Observable<AVObject>() {
  public void onSubscribe(Disposable disposable) {}

  public void onNext(AVObject avObject) {
    // 调用 fetchInBackground 和 refreshInBackground 效果是一样的。
  }

  public void onError(Throwable throwable) {}

  public void onComplete() {}
});

刷新操作会强行使用云端的属性值覆盖本地的属性。因此如果本地有属性修改,刷新操作会丢弃这些修改。

使用 CQL 语法获取对象

更新对象

LeanCloud 上的更新对象都是针对单个对象,云端会根据有没有 objectId 来决定是新增还是更新一个对象。

假如 objectId 已知,则可以通过如下接口从本地构建一个 AVObject 来更新这个对象:

// 第一参数是 className,第二个参数是 objectId
AVObject todo = AVObject.createWithoutData("Todo", "558e20cbe4b060308e3eb36c");

// 修改 content
todo.put("content","每周工程师会议,本周改为周三下午3点半。");
// 保存到云端
todo.saveInBackground().blockingSubscribe();

更新数组

更新计数器

按条件更新对象

通过使用 保存选项 query 可以按照指定条件去更新对象——当条件满足时,执行更新;条件不满足时,不执行更新。

例如:用户的账务账户表 Account 有一个余额字段 balance,同时有多个请求要修改该字段值,为避免余额出现负值,只有满足 balance >= 当前请求的数值 这个条件才允许修改,否则提示「余额不足,操作失败!」。

final int amount = -100;
AVQuery<AVObject> query = new AVQuery<>("Account");
AVObject account = query.getFirst();
account.increment("balance", amount);
AVSaveOption option = new AVSaveOption();
option.query(new AVQuery<>("Account").whereGreaterThanOrEqualTo("balance", -amount));
option.setFetchWhenSave(true);
account.saveInBackground(option).subscribe(new Observer<AVObject>() {
  public void onSubscribe(Disposable disposable) {}

  public void onNext(AVObject avObject) {
    System.out.println("当前余额为:" + avObject.get("balance"));
  }

  public void onError(Throwable throwable) {
    System.out.println("余额不足,操作失败!");
  }

  public void onComplete() {}
});

使用 CQL 语法更新对象

LeanCloud 提供了类似 SQL 语法中的 Update 方式更新一个对象,例如更新一个 TodoFolder 对象可以使用下面的代码:

AVQuery.doCloudQueryInBackground("update TodoFolder set name='家庭' where objectId='558e20cbe4b060308e3eb36c'").subscribe(new Observer<AVCloudQueryResult>() {
  public void onSubscribe(Disposable disposable) {}

  public void onNext(AVCloudQueryResult queryResult) {
    
  }

  public void onError(Throwable throwable) {
    
  }
});

删除对象

假如某一个 Todo 完成了,用户想要删除这个 Todo 对象,可以如下操作:

todo.deleteInBackground().blockingSubscribe();

删除对象是一个较为敏感的操作。在控制台创建对象的时候,默认开启了权限保护,关于这部分的内容请阅读: 角色与 ACL 权限管理

使用 CQL 语法删除对象

LeanCloud 提供了类似 SQL 语法中的 Delete 方式删除一个对象,例如删除一个 Todo 对象可以使用下面的代码:

// 执行 CQL 语句实现删除一个 Todo 对象
AVQuery.doCloudQueryInBackground("delete from Todo where objectId='558e20cbe4b060308e3eb36c'")
  .subscribe(new Observer<AVCloudQueryResult>() {
    public void onSubscribe(Disposable disposable) {}

    public void onNext(AVCloudQueryResult queryResult) {
      
    }

    public void onError(Throwable throwable) {
      
    }
});

批量操作对象

为了减少网络交互的次数太多带来的时间浪费,你可以在一个请求中对多个对象进行创建、更新、删除、获取。接口都在 AVObject 这个类下面:

// 批量创建、更新
saveAll()
saveAllInBackground()

// 批量删除
deleteAll()
deleteAllInBackground()

// 批量获取
fetchall()
fetchAllInBackground()

批量设置 Todo 已经完成:

AVQuery<AVObject> query = new AVQuery<>("Todo");
query.findInBackground().subscribe(new Observer<List<AVObject>>() {
  public void onSubscribe(Disposable disposable) {}

  public void onNext(List<AVObject> list) {
    for (AVObject todo : list) {
      todo.put("status", 1);
    }
    AVObject.saveAllInBackground(list).subscribe(new Observer<AVNull> () {
      public void onSubscribe(Disposable disposable) {}

      public void onNext(AVNull avNull) {
      }

      public void onError(Throwable throwable) {
      }

      public void onComplete() {}
    });
  }

  public void onError(Throwable throwable) {
    System.out.println("余额不足,操作失败!");
  }

  public void onComplete() {}
});

离线存储对象

大多数保存功能可以立刻执行,并通知应用「保存完毕」。不过若不需要知道保存完成的时间,则可使用 saveEventually 来代替。

它的优点在于:如果用户目前尚未接入网络,saveEventually会缓存设备中的数据,并在网络连接恢复后上传。如果应用在网络恢复之前就被关闭了,那么当它下一次打开时,LeanCloud 会再次尝试保存操作。

所有 saveEventually(或 deleteEventually)的相关调用,将按照调用的顺序依次执行。因此,多次对某一对象使用 saveEventually 是安全的。

数据关联

AVRelation(已弃用)

对象可以与其他对象相联系。如前面所述,我们可以把一个 AVObject 的实例 A,当成另一个 AVObject 实例 B 的属性值保存起来。这可以解决数据之间一对一或者一对多的关系映射,就像关系型数据库中的主外键关系一样。

Pointer

Pointer 只是个描述并没有具象的类与之对应,它与 AVRelation 不一样的地方在于:AVRelation 是在一对多的「一」这一方(上述代码中的一指 TodoFolder)保存一个 AVRelation 属性,这个属性实际上保存的是对被关联数据多的这一方(上述代码中这个多指 Todo)的一个 Pointer 的集合。而反过来,LeanCloud 也支持在「多」的这一方保存一个指向「一」的这一方的 Pointer,这样也可以实现一对多的关系。

简单的说, Pointer 就是一个外键的指针,只是在 LeanCloud 控制台做了显示优化。

现在有一个新的需求:用户可以分享自己的 TodoFolder 到广场上,而其他用户看见可以给与评论,比如某玩家分享了自己想买的游戏列表(TodoFolder 包含多个游戏名字),而我们用 Comment 对象来保存其他用户的评论以及是否点赞等相关信息,代码如下:

AVObject comment = new AVObject("Comment");// 构建 Comment 对象
comment.put("likes", 1);// 如果点了赞就是 1,而点了不喜欢则为 -1,没有做任何操作就是默认的 0
comment.put("content", "这个太赞了!楼主,我也要这些游戏,咱们团购么?");// 留言的内容

// 假设已知了被分享的该 TodoFolder 的 objectId 是 5590cdfde4b00f7adb5860c8
comment.put("targetTodoFolder", AVObject.createWithoutData("TodoFolder", "5590cdfde4b00f7adb5860c8"));
// 以上代码就是的执行结果就会在 comment 对象上有一个名为 targetTodoFolder 属性,它是一个 Pointer 类型,指向 objectId 为 5590cdfde4b00f7adb5860c8 的 TodoFolder

获取 Pointer 对象

当 Todo 拥有一个字段叫做 TodoFolder 的 Pointer 类型的属性,在获取 Todo 的对象的同时,想一并把被关联的 TodoFolder 也拉取到本地。更多内容可参考关联数据查询

地理位置

地理位置是一个特殊的数据类型,LeanCloud 封装了 AVGeoPoint 来实现存储以及相关的查询。

首先要创建一个 AVGeoPoint 对象。例如,创建一个北纬 39.9 度、东经 116.4 度的 AVGeoPoint 对象(LeanCloud 北京办公室所在地):

AVGeoPoint point = new AVGeoPoint(39.9, 116.4);

假如,添加一条 Todo 的时候为该 Todo 添加一个地理位置信息,以表示创建时所在的位置:

todo.put("whereCreated", point);

中间表(关联表)

有时我们需要知道更多关系的附加信息,比如在一个学生选课系统中,我们要了解学生打算选修的这门课的课时有多长,或者学生选修是通过手机选修还是通过网站操作的,此时我们可以使用传统的数据模型设计方法「中间表」。为此,我们创建一个独立的表 StudentCourseMap 来保存 StudentCourse 的关系:

字段 类型 说明
course Pointer Course 指针实例
student Pointer Student 指针实例
duration Array 所选课程的开始和结束时间点,如 ["2016-02-19","2016-04-21"]。
platform String 操作时使用的设备,如 iOS。
如此,实现选修功能的代码如下:
AVObject studentTom = new AVObject("Student");// 学生 Tom
studentTom.put("name", "Tom");

AVObject courseLinearAlgebra = new AVObject("Course");
courseLinearAlgebra.put("name", "线性代数");

AVObject studentCourseMapTom = new AVObject("StudentCourseMap");// 选课表对象

// 设置关联
studentCourseMapTom.put("student", studentTom);
studentCourseMapTom.put("course", courseLinearAlgebra);

// 设置学习周期
studentCourseMapTom.put("duration", Arrays.asList("2016-02-19", "2016-04-21"));
// 获取操作平台
studentCourseMapTom.put("platform", "iOS");

// 保存选课表对象
studentCourseMapTom.saveInBackground().blockingSubscribe();

查询选修了某一课程的所有学生:

// 微积分课程
AVObject courseCalculus = AVObject.createWithoutData("Course", "562da3fdddb2084a8a576d49");

// 构建 StudentCourseMap 的查询
AVQuery<AVObject> query = new AVQuery<>("StudentCourseMap");

// 查询所有选择了线性代数的学生
query.whereEqualTo("course", courseCalculus);

// 执行查询
query.findInBackground().subscribe(new Observer<List<AVObject>>() {
  public void onSubscribe(Disposable disposable) {

  }

  public void onNext(List<AVObject> list) {
    // list 是所有 course 等于线性代数的选课对象
    // 然后遍历过程中可以访问每一个选课对象的 student,course,duration,platform 等属性
    for (AVObject studentCourseMap : list) {
        AVObject student = studentCourseMap.getAVObject("student");
        AVObject course = studentCourseMap.getAVObject("course");
        ArrayList duration = (ArrayList) studentCourseMap.getList("duration");
        String platform = studentCourseMap.getString("platform");
    }
  }

  public void onError(Throwable throwable) {
    fail();
  }

  public void onComplete() {

  }
});

同样我们也可以很简单地查询某一个学生选修的所有课程,只需将上述代码变换查询条件即可:

AVQuery<AVObject> query = new AVQuery<>("StudentCourseMap");
AVObject studentTom = AVObject.createWithoutData("Student", "562da3fc00b0bf37b117c250");
query.whereEqualTo("student", studentTom);

序列化与本地缓存

AVParcelableObject 与 Android 的对象序列化

AVObject 及其子类如果要想在 Android 的 Activity 之间传递的话,需要使用 AVParcelableObject 来进行封装,其使用方法如下:

  • 将 AVObject(或其子类)放入 intent
    AVObject student = new AVObject("Student");
    student.put("age", 12);
    student.put("name", "Mike");
    AVParcelableObject parcelableObject = new AVParcelableObject(student);
    Intent intent = new Intent(this, ObjectTransferTargetActivity.class);
    intent.putExtra("attached", parcelableObject);
    this.startActivity(intent);
  • 在目标 activity 中获取 AVObject(或其子类)
    AVParcelableObject parcelableObject = getIntent().getParcelableExtra("attached");
    if (null != parcelableObject) {
      attached = parcelableObject.object();
      System.out.println(attached);
    } else {
      System.out.println("parcelableObject is null.");
    }

本地缓存

扩展:数据协议

很多开发者在使用 LeanCloud 初期都会产生疑惑:客户端的数据类型是如何被云端识别的? 因此,我们有必要重点介绍一下 LeanStorage 的数据协议。

先从一个简单的日期类型入手,比如在 Android 中,默认的日期类型是 Date,下面会详细讲解一个 Date 是如何被云端正确的按照日期格式存储的。 为一个普通的 AVObject 的设置一个 Date 的属性,然后调用保存的接口。Java SDK 在真正调用保存接口之前,会自动的调用一次序列化的方法,将 Date 类型的数据,转化为如下格式的数据:

{
  "__type": "Date",
  "iso": "2015-11-21T18:02:52.249Z"
}

然后发送给云端,云端会自动进行反序列化,这样自然就知道这个数据类型是日期,然后按照传过来的有效值进行存储。因此,开发者在进阶开发的阶段,最好是能掌握 LeanStorage 的数据协议。如下表介绍的就是一些默认的数据类型被序列化之后的格式:

类型 序列化之后的格式
{{dateType}} {"__type": "Date","iso": "2015-11-21T18:02:52.249Z"}
{{byteType}} {"__type": "Bytes","base64":"utf-8-encoded-string}"
Pointer {"__type":"Pointer","className":"Todo","objectId":"55a39634e4b0ed48f0c1845c"}
{{relationObjectName}} {"__type": "Relation","className": "Todo"}