mORMot 1.18 第11章 JSON - JavaScript对象表示法
JSON是一种用于指定数据布局和数组的行业尺度格式。(它是ECMA 404的一个子集。)虽然它最初是在JavaScript语言中界说的,但由于以下原因,它已成为一种流行的互联网格式,用于指定和互换数据:
- 它很紧凑,利用的数据字节比其他大多数格式都少
- 当增长足够的空格时,人类可以很容易地阅读
- 它解析效率高,因此可以非常快速地完成解析
其他替换方案是通用的XML(在REST/HTTP中是JSON的替换方案),或ISO OSI网络模型中的ASN.1/BER(用于LDAP、SNMP和其他一些互联网和OSI协议)。
比方,考虑一个名字数组的JSON。- {
- "employees": [{
- "firstName": "John",
- "lastName": "Doe"
- },
- {
- "firstName": "Anna",
- "lastName": "Smith"
- },
- {
- "firstName": "Peter",
- "lastName": "Jones"
- }
- ]
- }
复制代码 利用XML的替换方案是:- <employees>
- <employee>
- <firstName>John</firstName>
- <lastName>Doe</lastName>
- </employee>
- <employee>
- <firstName>Anna</firstName>
- <lastName>Smith</lastName>
- </employee>
- <employee>
- <firstName>Peter</firstName>
- <lastName>Jones</lastName>
- </employee>
- </employees>
复制代码 显然,JSON版本占用的空间更少,而且许多人发现它更具可读性。
一种名为BSON的二进制编码版本因成功的MongoDB NoSQL数据库而流行。它只是盘算机优化的JSON版本。
mORMot具有非常快的JSON编码和解码功能。针对服务器的性能,Windows和Linux在Intel版本上进行了手动优化。开放平台版本是高效的Pascal。
JSON/BSON有许多用途。比方,您可能偶尔需要扩展一个字段以容纳比设计时假想的数据类型更多的数据。只要数据在范围内,就可以利用JSON在任何这样的字段中存储任何对象。
一些数据库,特殊是PostgreSQL和SQLite3,允许险些任何巨细的数据都得当在文本字段中。它们没有强加像varchar(3)这样的界说所隐含的严格限制。
MongoDB以JSON/BSON格式存储数据属性。我们将在NoSQL章节中介绍这一点。
在REST/HTTP/S协议中,以及通常在HTML5/JavaScript页面利用AJAX(异步JavaScript互换)时,JSON数据会在客户端和服务器之间发送。
它也是一种将数据临时保存到文件中以与其他步伐互换的优秀方式,是现代CSV(逗号分隔值)导入体系的替换方案。
11.1 与JSON之间的转换
在像类这样的对象和JSON之间转换的过程被称为序列化。为此,我们将利用TDocVariant,这是一种Delphi Variant类型。
TDocVariants由以下几部分组成:
- 名称/值对
- 值可以是任何类型,包罗:
- 值(dvObject子类型)
- 数组(dvArray子类型)
- 嵌套的TDocVariant(TDocVariant类型)
JSON的美妙之处在于它可以存储任何动态值对象的内容,你不需要对峙利用预界说的模式。你的对象可以嵌套到任何深度(受可用内存限制)。
赋值可以通过值或通过引用来进行。按值是默认的,也是最安全的,但当运行时必须复制一个大型JSON变量中的所有数据记载时,它的速度会较慢。按引用是最快的选择,并且可以立即进行引用计数赋值。
通事后期绑定在代码中访问属性险些没有速度损失。别的,序列化和反序列化速度非常快,占用的内存也非常少。
与TDynArray动态数组包装器集成,就像记载序列化一样。
这将在反面进行描述。
任何包含作为已发布属性的变体自界说类型的TSQLRecord实例都将被mORMot的焦点识别,并会自动正确地将所有支持的数据库序列化为JSON。数据将存储在文本列中,而不是作为BLOB存储。
别的,任何基于接口的SOA服务都可以或许利用或发布变体内容。
最后,变体实例与Delphi的IDE完全集成。当你在IDE调试器中显示一个变体时,它将显示为JSON。
在你的步伐中利用TDocVariants有两种方式:
- 作为常规的变体变量,然后利用后期绑定,或者更快的_Safe()来访问数据。
- 作为TDocVariants,然后返回一个带有variant(sampledocvariant)的实例。
以下是一个示例:- var
- V: variant;
- ...
- TDocVariant.New(V); // 或者稍微慢一点的 V := TDocVariant.New;
- V.name := 'John';
- V.year := 1972;
- // 现在V包含 {"name":"john","year":1982}
- writeln(V);
- var
- V1, V2: variant; // 作为任何变体存储
- ...
- V1 := _Obj(['name', 'John', 'year', 1972]);
- V2 := _Obj(['name', 'John', 'doc', _Obj(['one', 1, 'two', 2.5])]); // 包含嵌套对象
复制代码 然后,您可以通过两种方式将这些对象转换为JSON:
- 利用VariantSaveJson()函数,它直接返回一个UTF-8内容。这是快速的方法。
- 将变体实例转换为字符串。这种方法较慢,但有效。
- writeln(VariantSaveJson(V1));// 显式转换为RawUTF8
- writeln(V1); // 从变体隐式转换为字符串
- // 这两个命令都将写入'"name":"john","year":1982
- writeln(VariantSaveJson(V2)); // 显式转换为RawUTF8
- writeln(V2); // 从变体隐式转换为字符串
- // 这两个命令都将写入'{”name”:”john”,”doc”:{”one”:1,”two”:2.5}}
复制代码 在服务器代码中,您可能希望利用更快的方法,但如果您忘记在客户端中利用它,可能不会有太大的区别。
请记着,键名是在运行时确定的,因此如果您在键名上打错字,很可能会收到错误。
您可以利用Exists方法来测试键的存在:- If not V1.Exists('name') then
- V1.name := 'John';
复制代码 请留意,您可以通过分配或重新分配值来轻松替换任何值。- V1.name := 'Joe';
- V1.name := 'Joclyn';
复制代码 您还可以利用V1.ToJSON进行反序列化,并且可以利用V1.Delete(keyname)开释/删除/删除元素。
可以界说数组:- V1 := _Arr(['John','Mark','Luke']);
- V2 := _Obj(['name','John','array', _Arr(['one','two',2.5])]); // 作为嵌套数组
- // _Arr() 较慢,适用于客户端,_FastArr() 是服务器的选项。
复制代码 如果您已经了解JSON,那么有一种高效的方法可以天生TDocVariants。- var
- V1,V2,V3,V4: variant; // 存储为任何变体
- ...
- V1 := _Json('{"name":"john","year":1982}'); // 严格的JSON语法
- V2 := _Json('{"name:"john",year:1982}'); // MongoDB扩展语法用于名称
- V3 := _Json('{"name":?,"year":?}',[],['john',1982]);
- V4 := _JsonFmt('{%:?,%:?}',['name','year'],['john',1982]);
- writeln(VariantSaveJSON(V1));
- writeln(VariantSaveJSON(V2));
- writeln(VariantSaveJSON(V3));
- writeln( V4 );
复制代码 所有这四个都会写入 { "name": "john", "year" : 1982 }
V3和V4的标志演示了变量的转达。
11.2 复制变体
通过_Obj()、_Arr()、_JSON()和_JSONFmt()创建的变体通常具有按值复制的模式,这意味着会将数据的副本放置在新变量中。这有两个重要影响:
- 性能较慢,尤其是当变体很大时
- 对变体副本所做的更改不会反映到实际对象上
比方:- V1 := _Obj(['name', 'John', 'year', 1973]);
- V2 := V1;
- V2.name := 'Josh';
- Writeln(V1.name, V2.name);
复制代码 这将同时显示John和Josh,由于变量是解耦的。
这四个函数还有一个可选的第二个参数dvoValueCopiedByReference,它将改变上述步伐的输出,以反映相同的耦合变量。
比方:- V1 := _Obj(['name', 'John', 'year', 1973], [dvoValueCopiedByReference]);
- V2 := V1;
- V2.name := 'Josh';
- Writeln(V1.name, V2.name);
复制代码 结果将是John和Josh。
_ObjFast、_ArrFast()、_JSONFast和_JSONFmtFast这四个函数只是调用同名函数的别名,但设置了dvoValueCopiedByReference参数。
实际上,在典范的Delphi编程中,当利用TObject后代时,您是通过引用来转达数据的,因此这应该是认识的领域。
您可以随时将TDocVariant更改为以下两种方式之一:
- 通过引用利用,调用_UniqueFast(variable)
- 通过值利用,调用_Unique(variable)
11.3 TDocVariant的用途
您会惊讶于TDocVariants的频繁利用。当您无法利用类或记载,由于您在设计时不知道所有字段时,它们非常有用。
mORMot将在任何界说了变体属性的TSQLRecord派生类中支持TDocVariants;并且它会自动将结果作为JSON存储在数据库的文本字段中。
mORMot在像NoSQL和日志记载这样的情况下利用TDocVariants,以及在所有可能的记载类型未预先界说且无法预先界说的情况下利用。TDocVarient为Delphi带来了一个无模式类,这更类似于Python或JavaScript等后期绑定语言。
TDocVariant是为HTML/JavaScript页面的AJAX查询提供JSON的自然方式。
11.4 数据分片
有时将JSON对象存储在文本字段中是有效的,这被称为分片。- type
- TSQLRecordData = class(TSQLRecord)
- private
- fName: RawUTF8;
- fData: variant;
- publishes
- property Name: RawUTF8 read fTest write fTest stored AS_UNIQUE;
- property Data: variant read } fData write fData;
- end;
复制代码 此记载中有三个字段:唯一ID、唯一名称和Data。- var
- aRec: TSQLRecordData;
- aID: TID;
- begin
- // 初始化一个记录
- aRec := TSQLRecordData.Create;
- aRec.Name := 'Joe'; // 一个唯一键
- aRec.Data := _JSONFast('{name:"Joe",age:30}');
- // 创建一个TDocVariant
- // 或者我们可以使用这种重载的构造函数来处理简单字段
- aRec := TSQLRecordData.Create(['Joe', _ObjFast(['name', 'Joe', 'age', 30])]);
- // 现在我们可以处理数据,例如通过后期绑定:
- writeln(aRec.Name); // 将输出 'Joe'
- writeln(aRec.Data); // 将输出 '{"name":"Joe","age":30\}'
- // (自动转换为JSON字符串)}
- aRec.Data.age := aRec.Data.age + 1; // 年龄增加一岁
- aRec.Data.interests := 'football'; // 向模式添加属性
- aID := aClient.Add(aRec, true); // 将存储{"name":"Joe","age":31,"interests":"football"}
- aRec.Free;
- // 现在我们可以通过aID创建的整数或通过Name='Joe'来检索数据
- end;
复制代码 在这里,我们已经将JSON数据存储在Data字段中。以下SQL函数可以从JSON描述的对象中返回属性。
JsonGet函数描述JsonGet(ArrColumn,0)从JSON数组中按索引返回属性值JsonGet(ObjColumn,'PropName')从JSON对象中按名称返回属性值JsonGet(ObjColumn,'Obj1.Obj2.Prop')通过路径(包罗嵌套的JSON对象)返回属性值JsonGet(ObjColumn,'Prop1,Prop2')从JSON对象中提取按名称指定的属性JsonGet(ObjColumn,'Prop1,Obj1.Prop')从JSON对象中提取按名称(包罗嵌套的JSON对象)指定的属性JsonGet(ObjColumn,'Prop*')从JSON对象中提取按通配符名称指定的属性JsonGet(ObjColumn,'Prop,Obj1.P')从JSON对象中提取按通配符名称(包罗嵌套的JSON对象)指定的属性比方:- JsonGet(ObjColumn,'owner') = {"login":"smith","id":123456} 作为文本
- JsonGet(ObjColumn,'owner.login') = "smith" 作为文本
- JsonGet(ObjColumn,'owner.id') = 123456 作为整数
- JsonGet(ObjColumn,'owner.name') = NULL
- JsonGet(ObjColumn,'owner.login,owner.id') ={"owner.login":"smith","owner.id":123456} 作为文本
- JsonGet(ObjColumn,'owner.I*') = {"owner.id":123456} 作为文本
- JsonGet(ObjColumn,'owner.*') = {"owner.login":"smith","owner.id":123456} 作为文本
- JsonGet(ObjColumn,'unknown.*') = NULL
- // 使用JsonHas返回True或False
- JsonHas(ObjColumn,'owner') = true
- JsonHas(ObjColumn,'owner.login') = true
- JsonHas(ObjColumn,'owner.name') = false
- JsonHas(ObjColumn,'owner.i*') = true
- JsonHas(ObjColumn,'owner.n*') = false
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |