IDocList/IDocDict JSON for Delphi and FPC
【英文原文】
多年来,我们的开源 mORMot 框架提供了多种方法来处理在运行时定义的数组/对象文档的恣意组合,比方通过 JSON,具有许多功能和非常高的性能。
我们的 TDocVariant自定义变体类型是处理这类无模式数据的一种强盛方式,但一些用户觉得它有些令人困惑。
因此,我们围绕它开辟了一套新的接口定义,以简化其使用,同时不牺牲其功能。我们围绕Python列表和字典对它们进行了建模,这已被证明是可行的——当然,也做了一些扩展。
TDocVariant的优缺点
多年来,我们的 TDocVariant可以存储任何基于JSON/BSON的文档内容,即:
- 面向对象文档的名/值对——在内部被标识为 dvObject子类型;
- 面向数组文档的值数组(包括嵌套文档)——在内部被标识为 dvArray子类型;
- 通过嵌套 TDocVariant实例,可以实现上述两者的恣意组合。
每个 TDocVariant实例也是一个自定义的变体类型:
- 因此,您可以将它存储或转换为变体变量;
- 您可以使用后期绑定来访问其对象属性,这在当代Pascal的严格天下中有点像把戏;
- Delphi IDE(和Lazarus 3.x)调试器对其有原生支持,因此可以将变体内容显示为JSON;
- 如果您在任何类或记录中定义了变体类型,我们的框架将辨认 TDocVariant内容,并将其序列化和反序列化为JSON,比方在其ORM、SOA或Mustache/MVC部分中。
这种强盛功能也带来了一些缺点:
- 在变体和其 TDocVariantData记录之间切换可能很棘手,有时需要一些令人困惑的指针引用;
- 每个 TDocVariant实例都可以用尴尬刁难其他数据的弱引用,大概维护其自身的内容——在某些极端情况下,不精确的使用可能会导致内存泄漏或GPF问题;
- TDocVariant可以是对象/字典或数组/列表,因此找到精确的方法可能很困难,大概在运行时引发异常;
- 它从一个简单的存储发展成了一个完备的内存引擎,因此高级功能通常被低估;
- TDocVariantData记录与大多数Delphi/FPC用户所习惯的类系统相去甚远;
- 默认情况下,不解析双精度值——只解析钱币值——如果你不想丧失任何精度,这是有意义的,但也被发现会造成混淆。
抱怨够了。
我们只需让它变得更好。
引入IDocList和IDocDict接口
我们引入了两个高级封装接口类型:
- IDocList(或其别名IDocArray)用于存储元素列表;
- IDocDict(或其别名IDocObject)用于存储键值对字典。
接口方法和定名遵循通常的Python列表和字典,并在安全且专用于类的IDocList和IDocDict类型中封装它们自己的TDocVariant存储。
您可能会在当代Delphi中如许写:- var
- list: IDocList;
- dict: IDocDict;
- v: variant;
- i: integer;
- begin
- // 从项目创建一个新的列表/数组
- list := DocList([1, 2, 3, 'four', 1.0594631]); // 默认情况下允许双精度值
- // 遍历列表
- for v in list do
- Listbox1.Items.Add(v); // 将变量转换为字符串
- // 或列表的一个子范围(使用类似Python的负索引)
- for i in list.Range(0, -3) do
- Listbox2.Items.Add(IntToStr(i)); // [1, 2] 作为整数
- // 搜索某些元素的存在
- assert(list.Exists(2));
- assert(list.Exists('four'));
- // 从JSON中获取一个对象列表,其中包含一个入侵者
- list := DocList('[{"a":0,"b":20},{"a":1,"b":21},"to be ignored",{"a":2,"b":22}]');
- // 枚举所有对象/字典,忽略非对象元素
- for dict in list.Objects do
- begin
- if dict.Exists('b') then
- ListBox2.Items.Add(dict['b']);
- if dict.Get('a', i) then
- ListBox3.Items.Add(IntToStr(i));
- end;
- // 删除一个元素
- list.Del(1);
- assert(list.Json = '[{"a":0,"b":20},"to be ignored",{"a":2,"b":22}]');
- // 提取一个元素
- if list.PopItem(v, 1) then
- assert(v = 'to be ignored');
- // 转换为JSON字符串
- Label1.Caption := list.ToString;
- // 显示 '[{"a":0,"b":20},{"a":2,"b":22}]'
- end;
复制代码 以及更多高级功能,如排序、搜索和表达式过滤:
[code]var v: variant; f: TDocDictFields; list, list2: IDocList; dict: IDocDict;begin list := DocList('[{"a":10,"b":20},{"a":1,"b":21},{"a":11,"b":20}]'); // 根据嵌套对象的字段对列表/数组进行排序 list.SortByKeyValue(['b', 'a']); assert(list.Json = '[{"a":10,"b":20},{"a":11,"b":20},{"a":1,"b":21}]'); // 使用条件表达式枚举列表/数组 for dict in list.Objects('b |