首页 > 其他分享 >IDocList/IDocDict JSON for Delphi and FPC

IDocList/IDocDict JSON for Delphi and FPC

时间:2024-05-23 13:30:33浏览次数:17  
标签:FPC Delphi list two assert JSON dict IDocList

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;

由于高级实例是接口,并且内部内容是变量,因此它们的使用寿命都是安全和正常的——您不需要编写任何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编程音乐时用过的东西……我仍然记得这个常数。

标签:FPC,Delphi,list,two,assert,JSON,dict,IDocList
From: https://www.cnblogs.com/hieroly/p/18208182

相关文章

  • Golang初学:vs code, launch.json, Run
    goversiongo1.22.1windows/amd64Windows11+amd64x86_64x86_64GNU/Linuxvscode1.89.1--- 序章在vscode开发go程序,之前总是在终端(terminal)输入命令(gorun.)来执行。不过,这不是最高效的方式。通过添加并配置launch.json可以更方便地程序仅运行(Ctrl+F5......
  • thinkphp5遇到必须使用Db::raw方法而无法使用json格式传递Db对象
    今天使用Thinkphp5做异步任务传递where参数时遇到一个问题:有一段如下代码:$where['jst.supplier']=['exp',Db::raw('>0orjst.is_supplier=1')];在使用swoole做异步任务时需要把where参数传递给异步任务处理,因为无法传递数组和对象只能传递字符串,所以需要把where数组转换......
  • JavaScript-和-JSON-基础知识-全-
    JavaScript和JSON基础知识(全)原文:zh.annas-archive.org/md5/256179285D6D80D91E6E7DA046AC4F3E译者:飞龙协议:CCBY-NC-SA4.0前言《JavaScript和JSON基础》是一个一站式资源,可用于理解和实现各种Web应用中的JSON。本书全面介绍了如何实现和集成JSON到您的应用程序......
  • Jackson 库中@JsonProperty和@JsonAlias注解实现序列化反序列化
    Json序列化一般为实体转化生成的JSON数据中直接包含嵌套对象的属性ObjectMappermapper=newObjectMapper();Bookbook=newBook("LearningJava","Java");Writerwriter=newWriter(110,"Mohit",book);StringjsonWriter=......
  • 聊聊 JSON Web Token (JWT) 和 jwcrypto 的使用
    哈喽大家好,我是咸鱼。最近写的一个Python项目用到了jwcrypto这个库,这个库是专门用来处理JWT的,JWT全称是JSONWebToken,JSON格式的Token。今天就来简单入门一下JWT。官方介绍:https://jwt.io/introduction先聊聊TokenToken的意思是令牌,通常用于身份验证。例如,......
  • Springboot Data Jdbc实体类json格式存储
    日常需求中有些需求需要在某字段存储json格式数据,例如日志审计接口传参数据等1.首先我们得保证数据库字段为text或者json2.设置读转换和写转换器importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.core.convert.converter.Converter;importo......
  • Java常用的JSON序列化与反序列化工具实践
    JSON简介:JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,通常用于在不同系统之间传输数据。它基于JavaScript对象语法,但已成为一种独立于语言的格式。JSON数据以键值对的形式组织,易于阅读和编写。为什么要使用JSON?1.简单易用:JSON的语法简单,易于理解和编写,可以......
  • jsoncpp按写入顺序读取
    jsoncpp按写入顺序读取在不修改jsoncpp源码的基础上,按照写入顺序读取,编写JsonValue类派生自Json::Value。jsonvalue.h#ifndefJSONVALUE_H#defineJSONVALUE_H#include<jsoncpp/json/json.h>classJsonValue:publicJson::Value{public:staticinlineJson::Val......
  • kingbase数据json操作:表转json、json转表、节点查询、节点添加
    1、json_array_elements(json)这个函数将JSON数组转换为行集合。例如:SELECTjson_array_elements('[1,2,3]')ASelement;将返回一个包含每个数组元素的行。2、json_each(json)这个函数将JSON对象展开为(key,value)对。例如:SELECT*FROMjson_each('{"a":1,"b":2}');......
  • 使用Spring HttpExchange时数据对象遇LocalDateTime字段类型json反序列化失败的解决方
    方法:重写MessageConverter,使得yyyy-MM-ddHH:mm:ss的字符串能反序列化到LocalDateTime类型上。@ConfigurationpublicclassHttpClientConfig{@Value("${service.host}")privateStringhost;@BeanRestClient.BuilderrestClientBuilder(){r......