delphi djson 类与JSON 互转,与 Java、Golang 一致写法

前因

为什么要开发这个 JSON库?原因是 delphi 官方的 json 既没有处理 null(也叫零值)的问题;举例说明吧:

开发者 往往 需要 类与JSON 之间 进行序列化 和 反序列化;接下来我们举个例子:

Person {

id: Int64; // ID

name: string; //姓名

desc: string; //描述

}

这样一个类 在 不同 语言里 如何 与 json 进行序列化 和 反序列化;

java 语言

在Java中,我们使用 json 往往很方便,因为 Java的 基本类型是包装类,具有自动装箱和拆箱功能,可以轻易解决 零值的问题;fastjson gson jackson 都可以,以 jackson举例:

public class Person {
    private Long id;
    private String name;
    private String desc;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

开始序列化和反序列化:

public class Test {
    public static void main(String[] args) throws JsonProcessingException {

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        Person person = new Person();
        person.setId(123L);
        person.setName("欧阳疯");
        //person.setDesc(""); 不设置,让其忽略 null 值

        //序列化
        String json = objectMapper.writeValueAsString(person);
        System.out.println(json); //{"id":123,"name":"欧阳锋"} 已忽略了 null值

        //反序列化
        Person person1 = objectMapper.readValue(json, Person.class);
        if(person1.getDesc() != null){ //反序列化后通过这个可以判断是否是nil值
            System.out.println("有设置");
        }else {
            //说明这个字段是未设置状态
            System.out.println("nil");
        }
    }
}

image

golang 语言

package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Id   string  `json:"id"`
	Name string  `json:"name,omitempty"`
	Desc *string `json:"desc,omitempty"` //注意这里使用 *string + omitempty 来解决零值的问题
}

func main() {
	// 赋值给 Person 结构体
	person := Person{
		Id:   "123",
		Name: "欧阳疯",
		//Desc: &desc, 使用指针赋值
	}

	//序列化为 JSON
	jsonData, err := json.Marshal(person)
	if err != nil {
		return
	}
	fmt.Println(string(jsonData)) //结果:{"id":"123","name":"欧阳疯"} 可见golang的序列化已给开发者忽略了 Nil值

	//反序列化
	var person1 Person
	err = json.Unmarshal([]byte(jsonData), &person1)
	if err != nil {
		return
	}
	if person1.Desc != nil { //通过这样就可以判断是否是nil
		fmt.Println("有设置")
	} else {
		fmt.Println("nil")
	}
}

image

可见 golang 官方的 json库 针对 基本类型的指针类型,也自动给开发者专一处理了;也非常方便使用 json 与 结构体之间的转换;

delphi 语言

例子就不上了,目前无论是官方的 System.JSON 还是其它第三方的,都做不到 类 与 Json 的互转,并同时处理 ”零值“ 的问题;之所以不想上官方的例子了,是因为官方的 System.JSON 使用起来非常复杂,仅仅是一个 类的序列化 都需要很长的代码,甚至还得手工写字段名;估计这个 System.JSON 也没有人使用吧,为了节省篇幅,不上官方的例子了;

djson

在以上背景下,我们知道了 Java、C# 这类具有基本类型的包装类,自动拆箱和装箱的语言,对于JSON里的 null 值天生解决了;而对于 golang、c++、delphi 这类没有包装类的语言,就得想法解决了,最好是语言层面给解决;golang 就是这样,通过 指针 + golang官方的注解,就解决掉了 nil 的问题;而 delphi 目前官方的 json 库,并没有给出解决方案;于是就开发了 djson,设计JSON底层数据模型,并大量使用了 向量化(SIMD)指令(System.Math.Vectors)来加速;还是上面的那个例子,我们看下 djson 如何实现,用 delphi 可以实现类似 Java 的写法,也可以实现 类似 golang的写法,个人感觉 还是 Java的写法 更优雅些,决定采用 Java 的写法了;

  1. 与Java 一样,先定义一个 Person 类;
unit person;

interface

uses System.SysUtils;

type
  TPerson = class
  private
    id: PInt64;
    name: PString;
    desc: PString;
  public
    destructor Destroy; override;
    function idIsNull: Boolean;
    function getId: Int64;
    procedure setId(value: Int64);
    function nameIsNull: Boolean;
    function getName: string;
    procedure setName(value: string);
    function descIsNull: Boolean;
    function getDesc: string;
    procedure setDesc(value: string);
  end;

implementation

const
  //可以定义在公共单元里
  nullMsg = '开发人员错误,此字段可能为Null,获取前要进行fieldIsNull判断,从而给其默认值或做出处理!';

destructor TPerson.Destroy;
begin
  if id <> nil then
  begin
    Dispose(id);
  end;
  if name <> nil then
  begin
    Dispose(name);
  end;
  if desc <> nil then
  begin
    Dispose(desc);
  end;
  inherited;
end;

function TPerson.idIsNull: Boolean;
begin
  Result := (id = nil);
end;

function TPerson.getId: Int64;
begin
  if id = nil then
  begin
    raise Exception.Create(nullMsg);
  end;
  Result := id^;
end;

procedure TPerson.setId(value: Int64);
begin
  if id = nil then
  begin
    New(id);
  end;
  id^ := value;
end;

function TPerson.nameIsNull: Boolean;
begin
  Result := (name = nil);
end;

function TPerson.getName: string;
begin
  if name = nil then
  begin
    raise Exception.Create(nullMsg);
  end;
  Result := name^;
end;

procedure TPerson.setName(value: string);
begin
  if name = nil then
  begin
    New(name);
  end;
  name^ := value;
end;

function TPerson.descIsNull: Boolean;
begin
  Result := (desc = nil);
end;

function TPerson.getDesc: string;
begin
  if desc = nil then
  begin
    raise Exception.Create(nullMsg);
  end;
  Result := desc^;
end;

procedure TPerson.setDesc(value: string);
begin
  if desc = nil then
  begin
    New(desc);
  end;
  desc^ := value;
end;

end.
  1. 开始序列化 和 反序列化

    uses djson, person;
    
    procedure TFormMain.Button1Click(Sender: TObject);
    begin
      var dj := TDJson.Create;
      try
        //序列化
        var person := TPerson.Create;
        person.setId(123);
        person.setName('欧阳疯');
        //person.setDesc() 不设置 忽略 null 值
    
        var json := dj.objToJson(person); //这一句 就行了
    
        Memo1.Lines.Add(json); //序列化后的结果
    
        //开始反序列化
        var person2 := TPerson.Create;
        dj.jsonToObj(json, person2);
    
        if not person2.descIsNull then
        begin
          Memo1.Lines.Add('有设置');
        end else begin //说明是null
          Memo1.Lines.Add('nil');
        end;
    
        person.Free;
        person2.Free;
      finally
        dj.Free;
      end;
    end;
    

可见写法 几乎 与 Java 一模一样了,与 golang 也基本一样!!!使用起来非常简单;关于 那个 person 类的代码生成,Java Ide 是可以轻松生成的,但是 delphi 的IDE 无法生成,你可以自己写一个 生成器(不是IDE插件),而是一个软件,专一生成这种格式的 delphi 类;这样以后 你就可以快速 生成 各种 delphi 类,并将其 与 json 互转,且有 ”零值" 如:nameIsNull 的处理;另外我上面给的这个例子是简单的,你完全可以类与类互相嵌套,组成复杂的 类 ,依然可以 与 JSON 互转,包括 array、枚举等 都可以互相嵌套的;我下一篇博客会做一个 复杂的 嵌套类的例子;

posted @ 2025-01-03 16:25  殴阳疯  阅读(224)  评论(0)    收藏  举报