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

news2024/11/17 1:36:31

目录

一、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
    // 美化JSON显示,将JSON数组内的项换行显示
    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;
begin
  Result := StringReplace(AJson, '},', '},' + sLineBreak, [rfReplaceAll]);
  Result := StringReplace(Result, '[{', '[' + sLineBreak + '{', [rfReplaceAll]);
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!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2049635.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NGINX 之 location 匹配优先级

章节 1 NGINX 的源码安装 2 NGINX 核心配置详解 3 NGINX 之 location 匹配优先级 4 NGINX 基础参数与功能 目录 1 location 基础语法 1.1 location 语法说明表 1.2 URI部分简单介绍 2 location 匹配优先级 2.1 URI匹配的规则与顺序 2.2 精确匹配(location /1.txt) 2.3 区…

hutool发邮件功能如何配置SMTP服务器参数?

hutool发邮件的教程指南&#xff1f;hutool发邮件性能优化方法&#xff1f; Hutool作为一个轻量级的Java工具库&#xff0c;其邮件发送功能因其简单易用而受到广泛关注。AokSend将详细介绍如何通过配置SMTP服务器参数来实现Hutool发邮件的功能。 hutool发邮件&#xff1a;优势…

LinuxKernel开发

Linux Kernel简介 0. Linux历史 Linux内核&#xff08;英語&#xff1a;Linux kernel&#xff09;是一种开源的类Unix操作系统宏内核。整个Linux操作系统家族基于该内核部署在传统计算机平台&#xff08;如个人计算机和服务器&#xff0c;以Linux发行版的形式[7]&#xff09;…

【Unity实战】NavMeshAgent实现Strafe固定朝向移动

众所周知&#xff0c;NavMeshAgent一旦设定了destination&#xff0c;它就会直奔目标。但是在一些场景中&#xff0c;比如NPC是个射手&#xff0c;除了瞄准玩家&#xff0c;也需要走位。如果不加以处理&#xff0c;我们恐怕会遇见瞄准IK和朝向…难以言表的表现&#xff0c;直接…

达梦数据库的系统视图v$datafile

达梦数据库的系统视图v$datafile 达梦数据库的V$DATAFILE 是一个重要的系统视图&#xff0c;提供了有关数据库数据文件的信息。 V$DATAFILE 系统视图 V$DATAFILE 视图用于显示数据库中每一个数据文件的详细信息。通过查询这个视图&#xff0c;数据库管理员可以了解数据文件的…

从零搭建xxl-job(六):xxl-job执行器服务端的简单搭建

经过前面的学习&#xff0c;相信大家对XXL-JOB调度中心的核心知识点和定时任务的调度流程已经了如指掌了&#xff0c;接下来&#xff0c;我们的重心就要从调度中心过渡到定时任务程序了&#xff0c;接下来&#xff0c;我会为大家搭建起一个简易的可运行的定时任务的体系&#x…

spring揭秘06-Autowired自动绑定依赖及组件自动扫描

文章目录 【README】【1】基于xml配置文件版本的自动绑定【1.1】基于xml配置文件版本的自动绑定代码示例 【2】基于注解版本的自动绑定【2.1】根据类型匹配的注解版自动绑定Autowired【2.2】根据名称匹配的注解版自动绑定AutowiredQualifier【2.2.1】 示例代码 【2.3】关于Prim…

面向财商人群的AI垂直产品 —— AI股票助手

在数字化转型的大潮中,AI技术正在重塑各行各业,尤其是金融市场。对于那些渴望在瞬息万变的股市中保持敏锐洞察力的金融分析师、投资者及股票爱好者来说,一款强大而智能的工具显得尤为重要。今天,我们将向大家介绍一款专为财商人群打造的AI垂直产品——AI股票助手。 一、产…

mac如何恢复被同名替换掉的文件夹 mac文件被替换如何恢复

Mac系统一直以高性能遥遥领先其他的Windows系统&#xff0c;因此&#xff0c;Mac虽然价格远远高出其他的笔记本电脑&#xff0c;但是还是受到了一众用户的青睐。使用mac时&#xff0c;我们也经常会将一个文件命名为已经有了相同文件的文件名&#xff0c;且保存到同一个目标地址…

吴恩达机器学习-C2W3-多类分类

目标 在本实验中&#xff0c;您将探索一个使用神经网络进行多类分类的示例。 工具 您将使用一些绘图例程。它们存储在这个目录下的lab_utils_multiclass_TF.py中。 import numpy as np import matplotlib.pyplot as plt %matplotlib widget from sklearn.datasets import …

centos7.9最小化安装之后的配置与下载

一. 配置yum源 1.备份系统自带源文件 cd /etc/yum.repos.d/ mkdir bak mv *.repo bak 2. 配置阿里云yum源 若有wget wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo若没有wget&#xff0c;先直接把 http://mirrors.aliyun.com/re…

11.怎么做好一个动态标签页

效果 步骤 1.在Elementui找一个标签页组件 复制粘贴到代码 2.将他写活 将很多页面需要用的方法和变量写入store editableTabsValue: 2,editableTabs: [{title: 首页,name: index,},],addTab(state, tab) {if (state.editableTabs.findIndex(item > item.title tab.titl…

mysql5.7主从同步失败原因总结-windows

1&#xff0c;主库data文件复制到从库&#xff0c;之后主库要同步的实例data一定不要在修改&#xff1b; 1.1&#xff0c;修改之后就要重新覆盖一遍修改过的data 2&#xff0c;如果状态不对&#xff1a;一定要查看日志&#xff1b;比如slave_io_state是空时&#xff0c;需要查…

KEEPALIVED 全csdn最详细----理论+实验(干货扎实,包教会的)

环境准备 主机名IP虚拟IP&#xff08;VIP&#xff09;功能ka1172.25.254.10172.25.254.100keepalived服务ka2172.25.254.20172.25.254.100keepalived服务realserver1172.25.254.110web服务realserver2172.25.254.120web服务 注意一定要关闭selinux,和防火墙&#xff0c;不然在…

zabbix7.0 设置中文语言( Debian GNU/Linux 12)

本例为安装zabbix7.0 zabbix_server (Zabbix) 6.4.17 Revision c12261f00b4 15 July 2024, compilation time: Jul 15 2024 11:05:06 系统版本信息为 lsb_release -a No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 12 (bookworm) Rele…

防疫物资管理信息系统pf

TOC springboot379防疫物资管理信息系统pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的广泛…

【Django开发】前后端分离django美多商城项目第2篇:展示用户注册页面,1. 创建用户模块子应用【附代码文档】

全套笔记资料代码移步&#xff1a; 前往gitee仓库查看 感兴趣的小伙伴可以自取哦~ 本教程的知识点为&#xff1a; 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计&#xff1a; 视图原型 2. 具体…

【网络安全】SSO登录过程实现账户接管

未经许可,不得转载。 文章目录 正文正文 登录页面展示了“使用 SSO 登录”功能: 经分析,单点登录(SSO)系统的身份验证过程如下: 1、启动SSO流程:当用户点击按钮时,浏览器会发送一个GET请求到指定的URL: /idp/auth/mid-oidc?req=[UNIQUE_ID]&redirect_uri=[REDI…

在 Mac 上更改 24小时制时间显示

使用“日期与时间”设置设定或更改 Mac 上的日期和时间。如果日期和时间正确&#xff0c;那么电子邮件、信息和文件上的时间戳也是准确的。了解如何设定日期和时间。 若要更改这些设置&#xff0c;请选取苹果菜单 >“系统设置”&#xff0c;点按边栏中的“通用” &#x…

[星瞳科技]OpenMV使用时有哪些常见错误和解决办法?

常见代码错误 ImportError:no module named xxx 这个错误是Import错误&#xff0c;没有stepper这个模块。 原因&#xff1a; 你没有把stepper.py这个文件拖到你的板子里。见&#xff1a;模块的使用 拖过去之后&#xff0c;需要重启&#xff0c;使模块生效 MemoryError:FB …