目录
一、JSON序列化和反序列化简介
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有易读写、易压缩、独立于编程语言等特点,广泛应用于web应用、网络传输、日志记录、数据存储等领域。
JSON字符串示例:
{
"name": "John0",
"age": 0,
"address": "New York",
"emails": "john@example.com",
"mobile": "000000000000",
"job": "Engineer",
"company": "Acme Inc.",
"hobby": "Reading",
"skillList": [
{
"skillName": "Delphi 0",
"level": 3,
"desc": "熟练运用"
},
{
"skillName": "Delphi 1",
"level": 4,
"desc": "熟练运用"
},
{
"skillName": "Delphi 2",
"level": 5,
"desc": "熟练运用"
}
]
}
JSON 序列化是指将数据结构或对象状态转换为JSON格式字符串的过程;
JSON 反序列化是将JSON格式的字符串转换回其原始的数据结构或对象状态的过程。
二、Delphi序列化的两种方式
Delphi 11及更高版本提供了两种序列化方式:
- TJson(REST.Json)
- TJsonSerializer(System.JSON.Serializers)
创建 TSkill、TPerson 类,其中TPerson类中的FSkillList属性的类型为TList<TSkill>,以此演示序列化方法的使用。
type
TSkill = class
FSkillName: string;
FLevel: Integer;
FDesc: string;
constructor Create(ASkillName: string; ALevel: Integer; ADesc: string);
destructor Destroy; override;
end;
TPerson = class
private
FName: string;
FAge: Integer;
FAddress: string;
FEmails: string;
FMobile: string;
FJob: string;
FCompany: string;
FHobby: string;
FSkillList: TList<TSkill>;
public
constructor Create(AName: string; AAge: Integer; AAddress, AEmail, AMobile, AJob, ACompany, AHobby: string);
destructor Destroy; override;
property Name: string read FName write FName;
property Age: Integer read FAge write FAge;
property Address: string read FAddress write FAddress;
property Emails: string read FEmails write FEmails;
property Mobile: string read FMobile write FMobile;
property Job: string read FJob write FJob;
property Company: string read FCompany write FCompany;
property Hobby: string read FHobby write FHobby;
property SkillList: TList<TSkill> read FSkillList write FSkillList;
end;
1、TJson的使用
- TJson.ObjectToJsonString方法将类转为JSON字符串
// 实例化 TPerson 类
p := TPerson.Create('John0',
0,
'New York',
'john@example.com',
'000000000000',
'Engineer',
'Acme Inc.',
'Reading');
for j := 0 to 2 do
begin
skill := TSkill.Create('Delphi '+j.ToString, 3+j, '熟练运用');
p.SkillList.Add(skill);
end;
// 将 person 对象序列化为JSON字符串
Result := TJson.ObjectToJsonString(p);
序列化结果:(请注意:TList<T>类型序列化JSON位于listHelper节点下,具体在第五节中讨论)
{
"name": "John0",
"age": 0,
"address": "New York",
"emails": "john@example.com",
"mobile": "000000000000",
"job": "Engineer",
"company": "Acme Inc.",
"hobby": "Reading",
"skillList": {
"listHelper": [
{
"skillName": "Delphi 0",
"level": 3,
"desc": "熟练运用"
},
{
"skillName": "Delphi 1",
"level": 4,
"desc": "熟练运用"
},
{
"skillName": "Delphi 2",
"level": 5,
"desc": "熟练运用"
}
]
}
}
//如果TJson.ObjectToJsonString函数传TList<TPerson>对象,则生成JSON数组字符串
{
"ownsObjects": true,
"listHelper": [
{
"name": "John0",
"age": 0,
"address": "New York",
"emails": "john@example.com",
"mobile": "000000000000",
"job": "Engineer",
"company": "Acme Inc.",
"hobby": "Reading",
"skillList": {
"listHelper": [
{
"skillName": "Delphi 0",
"level": 3,
"desc": "熟练运用"
},
{
"skillName": "Delphi 1",
"level": 4,
"desc": "熟练运用"
},
{
"skillName": "Delphi 2",
"level": 5,
"desc": "熟练运用"
}
]
}
},
{
"name": "John1",
"age": 1,
"address": "New York",
"emails": "john@example.com",
"mobile": "000000000000",
"job": "Engineer",
"company": "Acme Inc.",
"hobby": "Reading",
"skillList": {
"listHelper": [
{
"skillName": "Delphi 0",
"level": 3,
"desc": "熟练运用"
},
{
"skillName": "Delphi 1",
"level": 4,
"desc": "熟练运用"
},
{
"skillName": "Delphi 2",
"level": 5,
"desc": "熟练运用"
}
]
}
}
]
}
- TJson.JsonToObject<T: class> 方法将JSON字符串反序列化为类对象
procedure TestGetPersonFromJson(jsonText: string);
var
p: TPerson;
begin
// 如果JSON是数组,则 personList := TJson.JsonToObject<TList<TPerson>>(jsonText);
p := TJson.JsonToObject<TPerson>(jsonText);
try
// 操作 p, 比如 showmessage(p.Name);
finally
p.Free;
end;
end;
2、TJsonSerializer的使用
- TJsonSerializer.Serialize<T>(const AValue: T) 将类对象序列化为JSON
proceduer TestObjectToJson: string;
var
serializer: TJsonSerializer;
APerson: TPerson;
begin
serializer := TJsonSerializer.Create;
try
serializer.Formatting := TJsonFormatting.Indented; // 格式化JSON,便于阅读
// 这里创建 APerson 对象并初始化
//...
// 将 person 对象序列化为JSON
result := serializer.Serialize<TPerson>(APerson);
finally
serializer.Free;
end;
end;
同理,如果需要序列化TList<TPerson>对象,则调用
serializer.Serialize<TList<TPerson>>(APersonList);
- TJsonSerializer.Deserialize<T>(const AJson: string) 将JSON字符串反序列化为类
proceduer TestJsonToObject(AText: string): TPerson;
var
serializer: TJsonSerializer;
begin
serializer := TJsonSerializer.Create;
try
// 将 JSON 对象反序列化为 TPerson 对象
// 同样,如果需要反序列化为 TList<TPerson>,则为 serializer.Deserialize<TList<TPerson>>(AText);
result := serializer.Deserialize<TPerson>(AText);
finally
serializer.Free;
end;
end;
3、使用注意事项
- 类属性名与JSON键值对的Key值的对应关系
Delphi 在System.JSON.Serializers单元提供了JsonNameAttribute、JsonIgnoreAttribute、JsonConverterAttribute等属性配置,以便定义类字段与JSON键值对的Key值等。
type
TSkill = class
private
[JsonName('skillName')] // JSON 字符串的Key值将显示为 skillName
FSkillName: string;
[JsonName('level')]
FLevel: Integer;
[JsonIgnore][JsonName('desc')] // [JsonIgnore] 表示 FDesc字段将不显示在JSON中
FDesc: string;
public
constructor Create(ASkillName: string; ALevel: Integer; ADesc: string);
destructor Destroy; override;
end;
- 序列化JSON时的配置项
TJsonSerializer配置项:
// 格式化JSON字符串,便于阅读
serializer.Formatting := TJsonFormatting.Indented;
以下为所有配置项(有些配置项的使用方法待研究 )
/// <summary> Specifies the handling for empty values in managed types like array and string([], '') </summary>
TJsonEmptyValueHandling = (Empty, Null);
/// <summary> Specifies the handling of text format for TDateTime values </summary>
TJsonDateFormatHandling = (Iso, Unix, FormatSettings);
/// <summary> Specifies the handling of time zone for TDateTime values </summary>
TJsonDateTimeZoneHandling = (Local, Utc);
/// <summary> Indicates whether tokens should be parsed as TDatetime or not </summary>
TJsonDateParseHandling = (None, DateTime);
/// <summary>
/// Specifies how strings are escaped when writing JSON text:
/// - Default: Only control characters (e.g. newline) are escaped
/// - EscapeNonAscii: All non-ASCII and control characters (e.g. newline) are escaped.
/// - EscapeHtml: HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped.
/// </summary>
TJsonStringEscapeHandling = (Default, EscapeNonAscii, EscapeHtml);
/// <summary>
/// Specifies float format handling options when writing special floating point numbers:
/// - String: Write special floating point values as strings in JSON, e.g. "NaN", "Infinity", "-Infinity".
/// - Symbol: Write special floating point values as symbols in JSON, e.g. NaN, Infinity, -Infinity. Note that this will produce non-valid JSON.
/// - DefaultValue: Write special floating point values as the property's default value in JSON.
/// </summary>
TJsonFloatFormatHandling = (&String, Symbol, DefaultValue);
/// <summary>
/// Specifies formatting options for the TJsonTextWriter.
/// - None: No special formatting is applied. This is the default.
/// - Indented: Causes child objects to be indented according to the TJsonTextWriter.Indentation and TJsonTextWriter.IndentChar settings.
/// </summary>
TJsonFormatting = (None, Indented);
/// <summary> Container types for JSON </summary>
TJsonContainerType = (None, &Object, &Array, &Constructor);
TJsonNullValueHandling = (Include, Ignore);
TJsonDefaultValueHandling = (Include, Ignore, Populate, IgnoreAndPopulate);
TJsonReferenceLoopHandling = (Error, Ignore, Serialize);
TJsonObjectCreationHandling = (Auto, Reuse, Replace);
TJsonTypeNameHandling = (None, Objects, Arrays, All, Auto);
TJson配置项:
使用序列化函数TJson.ObjectToJsonString(AObject: TObject; AOptions: TJsonOptions = CDefaultOptions)时,可设置AOptions可选项:
joIndentCaseCamel:显示的JSON字段名用驼峰命名法
joIndentCaseLower:显示的JSON字段名全部小写
joSerialFields:仅序列化类的私有字段
默认的配置项为:const CDefaultOptions = [joDateIsUTC, joDateFormatISO8601, joBytesFormatArray, joIndentCaseCamel, joSerialFields];
type
TJsonOption = (joIgnoreEmptyStrings, joIgnoreEmptyArrays,
joDateIsUTC, joDateFormatUnix, joDateFormatISO8601, joDateFormatMongo, joDateFormatParse,
joBytesFormatArray, joBytesFormatBase64,
joIndentCaseCamel, joIndentCaseLower, joIndentCaseUpper, joIndentCasePreserve,
joSerialFields, joSerialPublicProps, joSerialPublishedProps, joSerialAllPubProps);
TJsonOptions = set of TJsonOption;
- TList<T>与TArray<T>序列化的区别
如二(1)中生成的JSON字符串示例所示,TList<T>对象生产JSON字符串时,会附带 TList<T>类的字段 FListHelper 等节点,这在常规的json中是不需要的;但如果使用 TArray<T>,则不会出现多余的Json节点。
function SerializeObjectListToJson(): string;
var
i: integer;
serializer: TJsonSerializer;
arrPerson: TArray<TPerson>;
begin
Result := '';
serializer := TJsonSerializer.Create;
try
SetLength(arrPerson, 5);
// 循环添加 TPerson 对象
for i := 0 to 4 do
begin
arrPerson[i] := TPerson.Create();
end;
// 以数组的方式序列化,JSON字符串中不会出现 FListHelper 等节点
result := serializer.Serialize<TArray<TPerson>>(arrPerson);
finally
serializer.Free;
end;
end;
三、Delphi与GO序列化效率对比
1、GO语言JSON序列化方法
- 引入包
import "encoding/json"
- 调用方法
json.Marshal(v any) ([]byte, error)
- 代码实现
package main
import (
"encoding/json"
"fmt"
"strconv"
"time"
)
type Skill struct {
Name string `json:"name"`
Level int `json:"level"`
Category string `json:"category"`
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
Emails string `json:"emails"`
Mobile string `json:"mobile"`
Job string `json:"job"`
Company string `json:"company"`
Hobby string `json:"hobby"`
Skills []*Skill `json:"skills"`
}
func testJson(count int) {
people := make([]*Person, 0)
//生成10000个Person对象,添加到 []*Person 切片中
for i := 0; i < count; i++ {
skills := make([]*Skill, 0)
for j := 0; j < 3; j++ {
skill := &Skill{
Name: "Go",
Level: j,
Category: "熟练运用",
}
skills = append(skills, skill)
}
p := &Person{
Name: "John" + strconv.Itoa(i),
Age: i,
Address: "New York",
Emails: "john@example.com",
Mobile: "000000000000",
Job: "Engineer",
Company: "Acme Inc.",
Hobby: "Reading",
Skills: skills,
}
people = append(people, p)
}
//计时开始
start := time.Now()
//序列化JSON
_, err := json.Marshal(people)
if err != nil {
panic(err)
}
//计时结束
elapsed := time.Since(start)
fmt.Printf("序列化JSON 数据量: %v 耗时:%v ms\n", count, elapsed.Milliseconds())
// fmt.Println("序列化JSON:" + string(data))
}
func main() {
testJson(10)
testJson(1000)
testJson(10000)
testJson(50000)
testJson(100000)
}
2、Delphi 与 GO 序列化效率对比
- 测试环境:
设备名称 long
处理器 Intel(R) Core(TM) i5-6400 CPU @ 2.70GHz 2.71 GHz
机带 RAM 16.0 GB
系统类型 64 位操作系统, 基于 x64 的处理器
- 测试数据:
JSON数据的组成:单条json含有9个键值对,其中一个是数组形式(含有3个键值对)(示例见第一节或下面测试结果图中的显示)
分别测试JSON量为10、1000、10000和50000时序列化的耗时情况
- 测试结果:
Delphi TJsonSerializer 和 TJson 序列化耗时情况:
go语言JSON序列化耗时情况:
- 最终结论:
处理大数据量JSON序列化及反序列化的性能:GO > Delphi TJsonSerializer > Delphi TJson
在 Delphi 中建议使用 TJsonSerializer。
四、Delphi序列化源码初探
(持续补充中...)
对于TJson类,一直好奇序列化JSON时,是如何对应字段的[JsonName("name")]属性?经跟踪源码,发现在 REST.JsonReflect 单元的ConvertFieldNameToJson函数中进行了如下逻辑判断:
首先根据反射检查类的字段是否配置了JsonNameAttribute 属性,如果是,则按配置的字段名显示;如果没有配置 JsonNameAttribute,则去掉字段名前的“F”(Delphi类的私有字段一般以F开头),然后根据配置的 TJsonOptions 中的驼峰命名、全部小写等配置项来显示字段。
比如:FFullName='Elmo' => {"fullName":"Elmo"}
以下是 Delphi 源码片段:
unit REST.JsonReflect;
function TJSONConverter.ConvertFieldNameToJson(const AField: TRttiDataMember): string;
var
LAttribute: TCustomAttribute;
begin
// First check if JsonNameAttribute is applied. Take without conversion
Result := '';
for LAttribute in AField.GetAttributes do
begin
if LAttribute is JsonNameAttribute then
begin
Result := JsonNameAttribute(LAttribute).Value;
Break;
end;
end;
// No Name Attribute found, regular rules apply
if Result = '' then
begin
Result := AField.Name;
// Delphi Fieldname usually start with "F", which we don't want in JSON
if (AField is TRttiField) and Result.StartsWith('F', True) then
Result := Result.Remove(0, 1);
// Javascript (i.e. JSON) defaults to lower Camel case, i.e. first letter lower case
// FFullName='Elmo' => {"fullName":"Elmo"}
case IdentCase of
jicCamel:
Result := AnsiLowerCase(Result.Chars[0]) + Result.Substring(1);
jicUpper:
Result := AnsiUpperCase(Result);
jicLower:
Result := AnsiLowerCase(Result);
jicPreserve:
;
end;
end;
end;
五、Delphi 序列化的优化通用类
实际开发过程中,类的列表字段一般会定义为TList<T>,而不是TArray<T>,因为TList<T>更方便操作,但Delphi 序列化JSON时,TArray<T>更符合我们的要求。
为了结合二者的优点,既方便操作列表类,又能方便的序列化JSON,可以运用如下思路:
1、定义类列表属性时,分别用TList<T> 和 TArray<T>定义,将TList<T>字段的配置设置为[JsonIgnore],TArray<T>字段用作显示JSON键值
type
TPerson = class
private
[JsonIgnore] // 该字段不进行JOSN序列化
FSkillList: TList<TSkill>;
[JsonName('skillList')] // 用于显示json
FSkillArr: TArray<TSkill>;
...
2、在序列化前,为 FSkillArr 赋值,进而对 TArray<T> 序列化
FSkillArr := FSkillList.ToArray;
3、在反序列化后,用 FSkillArr 为 FSkillList 赋值
for i := Low(FSkillArr) to High(FSkillArr) do
begin
FSkillList.Add(FSkillArr[i]);
end;
为了提高扩展性,定义一个基类TBaseObjectToJson,包含两个虚方法,以供子类实现,处理子类中TList<T>等属性:
TBaseObjectToJson = class
// 将 TList<T> 的数据赋值到 TArray<T>
procedure SetArrayFromList; virtual; abstract;
// 将 TArray<T> 的数据赋值到 TList<T>
procedure SetArrayToList; virtual; abstract;
end;
通用JSON序列化类完整代码如下:
unit AppUtils.ObjectToJson;
interface
uses
System.SysUtils, System.Types, System.Classes, System.Generics.Collections,
System.JSON, System.JSON.Serializers, REST.Json, System.JSON.Types;
const
C_JSON_ERROR = 'JSON字符串格式不正确:%s';
type
// 实体类和JSON互转的基类
// 问题:Delphi 自带的 TJson、TJsonSerializer 在对 TList<T> 序列化和反序列化时会自动添加 FListHelper 等节点,不是常规格式的JSON
// 解决方法:因 Delphi 序列化和反序列化 TArray<T> 时无其他自带节点,是我们期待的格式
// 所以在序列化时,将 TList<T> 转为 TArray<T> ,只对 TArray<T> 属性序列化;反序列化时亦然
// 继承示例如下:
// TPerson = class(TBaseObjectToJson)
// private
// [JsonIgnore] // 该字段不进行JOSN序列化
// FSkillList: TList<TSkill>;
// [JsonName('skillList')]
// FSkillArr: TArray<TSkill>;
// public
// procedure SetArrayFromList; override;
// procedure SetArrayToList; override;
// end;
//
// SetArrayFromList、SetArrayToList 方法实现示例
// procedure TPerson.SetArrayFromList;
// begin
// inherited;
// if not Assigned(FSkillList) then
// Exit;
//
// FSkillArr := FSkillList.ToArray;
// end;
//
// procedure TPerson.SetArrayToList;
// var
// i: Integer;
// begin
// inherited;
// if not Assigned(FSkillList) then
// begin
// FSkillList := TList<TSkill>.Create;
// end;
//
// for i := Low(FSkillArr) to High(FSkillArr) do
// begin
// FSkillList.Add(FSkillArr[i]);
// end;
// end;
TBaseObjectToJson = class
// 将 TList<T> 的数据赋值到 TArray<T>
procedure SetArrayFromList; virtual; abstract;
// 将 TArray<T> 的数据赋值到 TList<T>
procedure SetArrayToList; virtual; abstract;
end;
// JSON 序列化和反序列化处理类
TJsonUtils = class
public
// 格式化JOSN,便于阅读
class function FormatJSON(AJson: String): String;
//------------使用 REST.Json 单元的 TJson 类进行序列化和反序列化(大数据量时效率低) ---------
// 将 TObject 转为 JSON
class function ObjectToJSON<T: class>(const AObject: T): string;
// 将 TList<T> 转为 JSON, 返回值位于 FListHelper 节点(TList自带的属性)
class function ObjectListToJSON<T: class>(const AObjects: TObjectList<T>): string;
// 将 JSON 转为 TList<T>
class function JsonToObjectList<T: class, constructor>(const AText: string): TObjectList<T>;
//------------使用 System.JSON.Serializers 单元的 TJsonSerializer 类进行序列化和反序列化(大数据量效率高) ---------
// 将 TObject 转为 JSON
class function SerializeObjectToJson<T: TBaseObjectToJson>(const AObject: T): string;
// 将 TList<T> 转为 JSON
class function SerializeObjectListToJson<T: TBaseObjectToJson>(const AObjects: TObjectList<T>): string;
// 将 JSON 转为 TObject
class function SerializeJsonToObject<T: TBaseObjectToJson, constructor>(const AText: string): T;
// 将 JSON 转为 TList<T>
class function SerializeJsonToObjectList<T: TBaseObjectToJson, constructor>(const AText: string): TObjectList<T>;
end;
implementation
{ TJsonUtils }
class function TJsonUtils.SerializeObjectListToJson<T>(const AObjects: TObjectList<T>): string;
var
serializer: TJsonSerializer;
LObject: T;
begin
Result := '';
serializer := TJsonSerializer.Create;
try
// serializer.Formatting := TJsonFormatting.Indented; // 格式化
for LObject in AObjects do
begin
// 将 TList<T> 赋值到 TArray<T>
TBaseObjectToJson(LObject).SetArrayFromList;
end;
result := serializer.Serialize<TArray<T>>(AObjects.ToArray);
finally
serializer.Free;
end;
end;
class function TJsonUtils.SerializeJsonToObject<T>(
const AText: string): T;
var
serializer: TJsonSerializer;
LJo: TJSONObject;
begin
Result := nil;
serializer := TJsonSerializer.Create;
try
LJo := TJSONObject(TJSONObject.ParseJSONValue(AText));
if not Assigned(LJo) or (LJo.IsEmpty) then
raise Exception.Create(Format(C_JSON_ERROR, [AText]));
// JSON对象转为实体类
Result := serializer.Deserialize<T>(AText);
// 将 TArray<T> 的数据赋值到 TList<T>
TBaseObjectToJson(Result).SetArrayToList;
finally
serializer.Free;
if Assigned(LJo) then
LJo.Free;
end;
end;
class function TJsonUtils.SerializeJsonToObjectList<T>(
const AText: string): TObjectList<T>;
var
serializer: TJsonSerializer;
LObject: T;
LArray: TJSONArray;
LValue: TJSONValue;
LList: TObjectList<T>;
begin
Result := nil;
LArray := nil;
LList := TObjectList<T>.Create;
serializer := TJsonSerializer.Create;
try
LArray := TJSONObject.ParseJSONValue(AText) as TJSONArray;
if LArray = nil then
raise Exception.Create(Format(C_JSON_ERROR, [AText]));
for LValue in LArray do
if LValue is TJSONObject then
begin
// JSON对象转为实体类
LObject := serializer.Deserialize<T>(LValue.ToJSON());
// 将 TArray<T> 的数据赋值到 TList<T>
TBaseObjectToJson(LObject).SetArrayToList;
LList.Add(LObject);
end;
Result := LList;
LList := nil;
finally
if Assigned(LArray) then
LArray.Free;
if Assigned(LList) then
LList.Free;
if Assigned(serializer) then
serializer.Free;
end;
end;
class function TJsonUtils.JsonToObjectList<T>(const AText: string): TObjectList<T>;
var
LObject: T;
LArray: TJSONArray;
LValue: TJSONValue;
LList: TObjectList<T>;
begin
Result := nil;
LList := TObjectList<T>.Create;
LArray := nil;
try
LArray := TJSONObject.ParseJSONValue(AText) as TJSONArray;
if LArray = nil then
raise Exception.Create(Format(C_JSON_ERROR, [AText]));
for LValue in LArray do
if LValue is TJSONObject then
begin
// JSON对象转为实体类
LObject := TJson.JsonToObject<T>(TJSONObject(LValue));
LList.Add(LObject);
end;
Result := LList;
LList := nil;
finally
if Assigned(LArray) then
LArray.Free;
if Assigned(LList) then
LList.Free;
end;
end;
class function TJsonUtils.ObjectToJSON<T>(const AObject: T): string;
begin
Result := TJson.ObjectToJsonString(AObject);
end;
class function TJsonUtils.ObjectListToJSON<T>(
const AObjects: TObjectList<T>): string;
var
LObject: T;
LArray: TJSONArray;
LElement: TJSONObject;
begin
LArray := TJSONArray.Create;
try
for LObject in AObjects do
begin
// 实体类转为JSON对象
LElement := TJson.ObjectToJsonObject(LObject);
LArray.AddElement(LElement);
end;
Result := LArray.ToJSON;
finally
LArray.Free;
end;
end;
class function TJsonUtils.SerializeObjectToJson<T>(
const AObject: T): string;
var
serializer: TJsonSerializer;
begin
Result := '';
serializer := TJsonSerializer.Create;
try
// serializer.Formatting := TJsonFormatting.Indented; // 格式化
// 将 TList<T> 赋值到 TArray<T>
TBaseObjectToJson(AObject).SetArrayFromList;
result := serializer.Serialize<T>(AObject);
finally
serializer.Free;
end;
end;
class function TJsonUtils.FormatJSON(AJson: String): String;
var
stringReader: TStringReader;
stringWriter: TStringWriter;
stringBuilder: TStringBuilder;
JsonWriter: TJsonTextWriter;
JsonReader: TJsonTextReader;
begin
stringReader := TStringReader.Create(AJson);
JsonReader := TJsonTextReader.Create(stringReader);
stringBuilder := TStringBuilder.Create;
stringWriter := TStringWriter.Create(stringBuilder);
JsonWriter := TJsonTextWriter.Create(stringWriter);
// 格式化JOSN,便于阅读
JsonWriter.Formatting := TJsonFormatting.Indented;
try
JsonWriter.WriteToken(JsonReader);
Result := stringBuilder.ToString;
finally
JsonWriter.Free;
JsonReader.Free;
stringReader.Free;
stringWriter.Free;
end;
end;
end.
以下为实体类及主窗体单元文件:
unit Entity.Person;
interface
uses
System.SysUtils, System.Types, System.Classes, System.Generics.Collections,
System.JSON.Serializers, AppUtils.ObjectToJson;
type
TSkill = class
private
// JSON 字符串的Key值将显示为 skillName,否则使用 TJsonSerializer 序列化时会显示 FSkillName
[JsonName('skillName')]
FSkillName: string;
[JsonName('level')]
FLevel: Integer;
[JsonIgnore][JsonName('desc')] // [JsonIgnore] 表示 FDesc字段将不显示在JSON中
FDesc: string;
public
constructor Create(ASkillName: string; ALevel: Integer; ADesc: string);
destructor Destroy; override;
end;
TPerson = class(TBaseObjectToJson)
private
[JsonName('name')]
FName: string;
[JsonName('age')]
FAge: Integer;
[JsonName('address')]
FAddress: string;
[JsonName('emails')]
FEmails: string;
[JsonName('mobile')]
FMobile: string;
[JsonName('job')]
FJob: string;
[JsonName('company')]
FCompany: string;
[JsonName('hobby')]
FHobby: string;
[JsonIgnore] // 该字段不进行JOSN序列化
FSkillList: TList<TSkill>;
[JsonName('skillList')]
FSkillArr: TArray<TSkill>;
public
constructor Create(AName: string; AAge: Integer; AAddress, AEmail, AMobile, AJob, ACompany, AHobby: string);
destructor Destroy; override;
property Name: string read FName write FName;
property Age: Integer read FAge write FAge;
property Address: string read FAddress write FAddress;
property Emails: string read FEmails write FEmails;
property Mobile: string read FMobile write FMobile;
property Job: string read FJob write FJob;
property Company: string read FCompany write FCompany;
property Hobby: string read FHobby write FHobby;
property SkillList: TList<TSkill> read FSkillList write FSkillList;
procedure SetArrayFromList; override;
procedure SetArrayToList; override;
end;
TPersonList = class(TList<TPerson>)
private
public
constructor Create;
destructor Destroy; override;
end;
implementation
{ TSkill }
constructor TSkill.Create(ASkillName: string; ALevel: Integer; ADesc: string);
begin
FSkillName := ASkillName;
FLevel := ALevel;
FDesc := ADesc;
end;
destructor TSkill.Destroy;
begin
inherited;
end;
{ TPerson }
constructor TPerson.Create(AName: string; AAge: Integer; AAddress, AEmail, AMobile, AJob, ACompany, AHobby: string);
begin
FName := AName;
FAge := AAge;
FAddress := AAddress;
FEmails := AEmail;
FMobile := AMobile;
FJob := AJob;
FCompany := ACompany;
FHobby := AHobby;
FSkillList := TList<TSkill>.Create;
end;
destructor TPerson.Destroy;
begin
if Assigned(FSkillList) then
begin
FSkillList.Clear;
FSkillList.Free;
end;
inherited;
end;
procedure TPerson.SetArrayFromList;
begin
inherited;
if not Assigned(FSkillList) then
Exit;
FSkillArr := FSkillList.ToArray;
end;
procedure TPerson.SetArrayToList;
var
i: Integer;
begin
inherited;
if not Assigned(FSkillList) then
begin
FSkillList := TList<TSkill>.Create;
end;
for i := Low(FSkillArr) to High(FSkillArr) do
begin
FSkillList.Add(FSkillArr[i]);
end;
end;
{ TPersonList }
constructor TPersonList.Create;
begin
inherited;
end;
destructor TPersonList.Destroy;
begin
inherited;
end;
end.
unit uMain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
System.JSON, System.Generics.Collections, REST.Json, FMX.Memo.Types,
FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls,
System.JSON.Serializers, Entity.Person, AppUtils.ObjectToJson, FMX.Layouts,
FMX.Objects;
type
TS = class(TForm)
btnJsonToObject: TButton;
btnObjToJson: TButton;
Layout1: TLayout;
mmoLog: TMemo;
Text1: TText;
mmoJson: TMemo;
procedure btnObjToJsonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnJsonToObjectClick(Sender: TObject);
private
{ Private declarations }
FPerSonList: TObjectList<TPerson>;
procedure GenerateListData(ACount: Integer);
procedure TestJSON(ADataCount: Integer; AShowJson: Boolean = FALSE);
public
{ Public declarations }
end;
var
S: TS;
implementation
{$R *.fmx}
procedure TS.btnJsonToObjectClick(Sender: TObject);
var
pList: TObjectList<TPerson>;
startTick, endTick: Cardinal;
begin
startTick := TThread.GetTickCount;
pList := TJsonUtils.JsonToObjectList<TPerson>(mmoJson.Text);
endTick := TThread.GetTickCount;
mmoLog.Lines.Add(Format('用 REST.JSON 反序列化 耗时:%d ms', [endTick-startTick]));
pList.Free;
startTick := TThread.GetTickCount;
pList := TJsonUtils.SerializeJsonToObjectList<TPerson>(mmoJson.Text);
endTick := TThread.GetTickCount;
mmoLog.Lines.Add(Format('用 TJsonSerializer 反序列化 耗时:%d ms', [endTick-startTick]));
pList.Free;
end;
procedure TS.btnObjToJsonClick(Sender: TObject);
begin
mmoJson.Lines.Clear;
mmoLog.Lines.Clear;
TestJSON(10, True);
//TestJSON(1000);
//TestJSON(10000, True);
//TestJSON(50000);
end;
procedure TS.FormCreate(Sender: TObject);
begin
FPerSonList := TObjectList<TPerson>.Create;
end;
procedure TS.FormDestroy(Sender: TObject);
begin
FPerSonList.Clear;
FPerSonList.Free;
end;
procedure TS.GenerateListData(ACount: Integer);
var
i, j: integer;
p: TPerson;
skill: TSkill;
begin
// 循环生成 Person 列表
for i := 0 to ACount-1 do
begin
p := TPerson.Create('John'+i.ToString,
i,
'New York',
'john@example.com',
'000000000000',
'Engineer',
'Acme Inc.',
'Reading');
// 添加技能
for j := 0 to 2 do
begin
skill := TSkill.Create('Delphi '+j.ToString, 3+j, '熟练运用');
p.SkillList.Add(skill);
end;
FPerSonList.Add(p);
end;
end;
procedure TS.TestJSON(ADataCount: Integer; AShowJson: Boolean = FALSE);
var
lJsonText: string;
startTick, endTick: Cardinal;
begin
// 生成 Person 列表
GenerateListData(ADataCount);
startTick := TThread.GetTickCount;
// 将 person 列表序列化为JSON
lJsonText := TJsonUtils.ObjectListToJSON<TPerson>(FPerSonList);
endTick := TThread.GetTickCount;
mmoLog.Lines.Add(Format('用 REST.JSON 的 TJSON 生成JSON 数据量:%d 耗时:%d ms', [ADataCount, endTick-startTick]));
// if AShowJson then
// mmoJson.Lines.Add(TJsonUtils.FormatJSON(lJsonText));
startTick := TThread.GetTickCount;
lJsonText := TJsonUtils.SerializeObjectListToJson<TPerson>(FPerSonList);
endTick := TThread.GetTickCount;
mmoLog.Lines.Add(Format('用 TJsonSerializer 生成JSON 数据量:%d 耗时:%d ms', [ADataCount, endTick-startTick]));
mmoLog.Lines.Add('-----------------------------------------------------------------------' + sLineBreak);
//mmoLog.Lines.Add('lJsonText字符串长度:' + lJsonText.Length.ToString);
if AShowJson then
mmoJson.Lines.Add(TJsonUtils.FormatJSON(lJsonText));
end;
end.
object S: TS
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 484
ClientWidth = 824
Position = DesktopCenter
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnCreate = FormCreate
OnDestroy = FormDestroy
DesignerMasterStyle = 0
object btnJsonToObject: TButton
Position.X = 208.000000000000000000
Position.Y = 73.000000000000000000
Size.Width = 161.000000000000000000
Size.Height = 49.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
Text = 'JSON'#21453#24207#21015#21270#20026#23545#35937
TextSettings.Trimming = None
OnClick = btnJsonToObjectClick
end
object btnObjToJson: TButton
Position.X = 32.000000000000000000
Position.Y = 73.000000000000000000
Size.Width = 145.000000000000000000
Size.Height = 49.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
Text = #23545#35937#24207#21015#21270#20026'JSON'
TextSettings.Trimming = None
OnClick = btnObjToJsonClick
end
object Layout1: TLayout
Align = Bottom
Position.Y = 136.000000000000000000
Size.Width = 824.000000000000000000
Size.Height = 348.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
object mmoLog: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = []
Align = Left
Size.Width = 393.000000000000000000
Size.Height = 348.000000000000000000
Size.PlatformDefault = False
TabOrder = 3
Viewport.Width = 389.000000000000000000
Viewport.Height = 344.000000000000000000
end
object mmoJson: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = []
Align = Client
Size.Width = 431.000000000000000000
Size.Height = 348.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
Viewport.Width = 427.000000000000000000
Viewport.Height = 344.000000000000000000
end
end
object Text1: TText
Align = Top
Size.Width = 824.000000000000000000
Size.Height = 65.000000000000000000
Size.PlatformDefault = False
Text = 'Delphi JSON'#24207#21015#21270#25928#29575#27979#35797
TextSettings.Font.Size = 20.000000000000000000
TextSettings.FontColor = claCoral
end
end
注:上述代码所用开发工具为Delphi12.1!