Delphi 实现JSON序列化和反序列化的功能以及源码探究

目录

一、JSON序列化和反序列化简介

二、Delphi序列化的两种方式

1、TJson的使用

2、TJsonSerializer的使用

3、使用注意事项 

三、Delphi与GO序列化效率对比

1、GO语言JSON序列化方法

2、Delphi 与 GO 序列化效率对比

四、Delphi序列化源码初探

五、Delphi 序列化的优化通用类 



一、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 (&lt;, &gt;, &amp;, &apos;, &quot;) 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!

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值