当前位置: 代码网 > it编程>编程语言>Delphi > IDocList/IDocDict JSON for Delphi and FPC

IDocList/IDocDict JSON for Delphi and FPC

2024年05月23日 Delphi 我要评论
IDocList/IDocDict JSON for Delphi and FPC 【英文原文】 多年来,我们的开源 mORMot 框架提供了多种方法来处理在运行时定义的数组/对象文档的任意组合,例如通过 JSON,具有许多功能和非常高的性能。 我们的 TDocVariant自定义变体类型是处理这类 ...

idoclist/idocdict json for delphi and fpc

【英文原文】

多年来,我们的开源 mormot 框架提供了多种方法来处理在运行时定义的数组/对象文档的任意组合,例如通过 json,具有许多功能和非常高的性能。

img

我们的 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; 

以及更多高级功能,如排序、搜索和表达式过滤:

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<21') do
    assert(dict.i['b'] < 21);

  // 使用变量作为条件表达式的另一个枚举
  for dict in list.objects('a=', 10) do
    assert(dict.i['a'] = 10);

  // 根据条件表达式创建新的idoclist
  list2 := list.filter('b =', 20);
  assert(list2.json = '[{"a":10,"b":20},{"a":11,"b":20}]');

  // 直接访问内部tdocvariantdata存储
  assert(list.value^.count = 3);
  assert(list.value^.kind = dvarray);
  assert(dict.value^.kind = dvobject);
 
  // 通过变量中介获取tdocvariantdata
  v := list.asvariant;
  assert(_safe(v)^.count = 3);
  v := dict.asvariant;
  assert(_safe(v)^.count = 2);

  // 类似python的高级方法
  if list.len > 0 then
    while list.popitem(v) do
    begin
      assert(list.count(v) = 0); // 计算出现的次数
      assert(not list.exists(v));
      listbox1.items.add(v.a); // 后期绑定 
      dict := docdictfrom(v); // 从变量转换为idocdict
      assert(dict.exists('a') and dict.exists('b'));
      // 枚举此字典的键值元素
      for f in dict do
      begin
        listbox2.items.add(f.key);
        listbox3.items.add(f.value);
      end;
    end;

  // 从任何复杂的“紧凑”json创建
  // (注意键名没有被“引用”)
  list := doclist('[{ab:1,cd:{ef:"two"}}]');

  // 我们仍然有后期绑定的魔法在工作
  assert(list[0].ab = 1);
  assert(list[0].cd.ef = 'two');

  // 从代码中提供的键值对创建字典
  dict := docdict(['one', 1, 'two', 2, 'three', _arr([5, 6, 7, 'huit'])]);
  assert(dict.len = 3); // 一个包含3个元素的字典
  assert(dict.json = '{"one":1,"two":2,"three":[5,6,7,"huit"]}');

  // 转换为带有美观格式(换行符和空格)的json
  memo1.caption := dic.tostring(jsonhumanreadable);

  // 按键名排序
  dict.sort;
  assert(dict.json = '{"one":1,"three":[5,6,7,"huit"],"two":2}');

  // 注意,它将在排序后确保更快的o(log(n))键查找:
  // (对于具有大量键的对象,这有利于提高性能)
  assert(dict['two'] = 2); // 作为变量值的默认查找
  assert(dict.i['two'] = 2); // 显式转换为整数
end;

以下是 ttextwriter.addjsonreformat()方法及其 jsonbufferreformat()jsonreformat()封装的可用json格式:

  • jsoncompact是默认的、对机器友好的单行布局
  • jsonhumanreadable会添加换行符和缩进,以获得更人性化的结果
  • jsonunquotedpropname将生成 jsonhumanreadable布局,但只在必要时才引用所有属性名称:此格式可用于配置文件等场合 - 此格式与mongodb扩展语法中使用的格式类似,与json不兼容:不要与ajax客户端等一起使用,但它会被我们的所有单元按预期处理为有效的json输入,而无需事先校正
  • jsonunquotedpropnamecompact将生成单行布局,其中包含未引用的属性名称,这是mormot实例中数据输出量最小的方式
  • 默认情况下,我们依赖于utf-8编码(在rfc 8259中是必需的),但您可以使用 jsonescapeunicode生成纯7位ascii输出,其中非ascii字符使用\u####转义,例如默认的python json.dumps
  • jsonnoescapeunicode会搜索任何\u####模式,并生成纯utf-8输出
  • 这些特性不是在此单元中实现,而是在mormot.core.json中实现

由于高级实例是接口,并且内部内容是变量,因此它们的使用寿命都是安全和正常的——您不需要编写任何try..finaly list.free代码。

而且性能仍然很高,因为例如一个巨大的json数组会分配一个单独的idoclist,所有嵌套的节点都将作为变体的有效动态数组来保存。

最后两行代码可能展示了我们的mormot库在delphi和fpc的json库森林/丛林中是如何独树一帜的:

assert(doclist('[{ab:1,cd:{ef:"two"}}]')[0].cd.ef = 'two');

assert(doclist('[{ab:1,cd:{ef:"two"}}]').first('ab<>0').cd.ef = 'two');

如果你与标准的delphi json库的工作方式进行比较,以及它与每个节点的类的工作方式,你可能会发现很大的不同!

请注意,这两行代码都可以用古老的delphi 7编译器进行编译和运行——谁说pascal语言在当年没有表现力?

我们希望我们成功地开辟了一种与json文档交互的新方式,这样你就可以在你的delphi或fpc项目中使用它。

一如既往,我们欢迎在我们的论坛中提供任何反馈!

顺便说一句,你知道我为什么在代码中选择1.0594631这个数字吗?

提示:这是我在小时候使用z80 cpu编程音乐时用过的东西……我仍然记得这个常数。😄

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com