Delphi 10.2内置JSON库销毁根对象出错,求优雅解决方案
Delphi 10.2 内置JSON库重复引用对象销毁错误的优雅解决方法
你的代码报错核心原因是同一JSON子对象被多次添加到不同父容器时,默认会被多个父容器同时声明拥有所有权。当销毁根对象aJsonObj时,会先释放aJsonArr,而aJsonArr会释放aJsonObj2和aJsonObj3;随后aJsonObj尝试释放自己a、b键对应的aJsonObj2、aJsonObj3,这就导致了重复释放已回收内存的错误。
不需要自行编写递归销毁的DestroyJSONObject过程,利用Delphi内置JSON库自带的功能就能优雅解决问题,以下是具体方案:
解决方案1:控制父容器的所有权(推荐)
Delphi的TJSONObject.AddPair和TJSONArray.Add都支持指定是否接管子对象的所有权(默认是True)。对于需要重复引用的子对象,只让其中一个父容器拥有所有权,其他容器设置AOwnsValue=False即可:
修改后的代码:
procedure TForm1.Button1Click(Sender: TObject); var aJsonObj,aJsonObj2,aJsonObj3 : TJsonObject; aJsonArr : TJsonArray; begin aJsonObj := TJsonObject.Create; aJsonObj2 := TJsonObject.Create; aJsonObj3 := TJsonObject.Create; aJsonArr := TJsonArray.Create; aJsonObj2.AddPair('aa','aa'); aJsonObj3.AddPair('bbb','bbb'); aJsonArr.Add(aJsonObj2); // 让数组拥有aJsonObj2、aJsonObj3的所有权 aJsonArr.Add(aJsonObj3); aJsonObj.AddPair('arr',aJsonArr); // 重复添加时设置不接管所有权 aJsonObj.AddPair('a',aJsonObj2, False); aJsonObj.AddPair('b',aJsonObj3, False); ShowMessage(aJsonObj.ToJSON); FreeAndNil(aJsonObj); // 释放根对象时,数组会释放aJsonObj2、aJsonObj3,不会重复释放 end;
解决方案2:克隆对象避免共享实例
如果业务上不需要共享同一个JSON对象实例,而是需要相同内容的独立副本,可以使用TJSONValue.Clone方法复制对象,让每个父容器拥有自己的副本:
修改后的代码:
procedure TForm1.Button1Click(Sender: TObject); var aJsonObj,aJsonObj2,aJsonObj3 : TJsonObject; aJsonArr : TJsonArray; begin aJsonObj := TJsonObject.Create; aJsonObj2 := TJsonObject.Create; aJsonObj3 := TJsonObject.Create; aJsonArr := TJsonArray.Create; aJsonObj2.AddPair('aa','aa'); aJsonObj3.AddPair('bbb','bbb'); aJsonArr.Add(aJsonObj2); // 数组拥有原对象 aJsonArr.Add(aJsonObj3); aJsonObj.AddPair('arr',aJsonArr); // 添加克隆后的副本,根对象拥有副本的所有权 aJsonObj.AddPair('a',aJsonObj2.Clone as TJSONObject); aJsonObj.AddPair('b',aJsonObj3.Clone as TJSONObject); ShowMessage(aJsonObj.ToJSON); FreeAndNil(aJsonObj); // 根对象释放副本,数组释放原对象,无冲突 end;
不推荐的方案:手动管理所有对象生命周期
如果一定要自己控制所有对象的释放,可以在所有添加操作中都设置AOwnsValue=False,最后手动释放所有创建的对象。但这种方式容易遗漏对象导致内存泄漏,仅适合特殊场景:
procedure TForm1.Button1Click(Sender: TObject); var aJsonObj,aJsonObj2,aJsonObj3 : TJsonObject; aJsonArr : TJsonArray; begin aJsonObj := TJsonObject.Create; aJsonObj2 := TJsonObject.Create; aJsonObj3 := TJsonObject.Create; aJsonArr := TJsonArray.Create; aJsonObj2.AddPair('aa','aa'); aJsonObj3.AddPair('bbb','bbb'); aJsonArr.Add(aJsonObj2, False); aJsonArr.Add(aJsonObj3, False); aJsonObj.AddPair('arr',aJsonArr, False); aJsonObj.AddPair('a',aJsonObj2, False); aJsonObj.AddPair('b',aJsonObj3, False); ShowMessage(aJsonObj.ToJSON); // 手动释放所有对象 FreeAndNil(aJsonObj); FreeAndNil(aJsonArr); FreeAndNil(aJsonObj2); FreeAndNil(aJsonObj3); end;
总结
优先推荐第一种方案(控制所有权),它既能保留对象的共享引用,又能避免重复释放的问题;如果需要独立的对象实例,则选择克隆方案。
内容的提问来源于stack exchange,提问作者Serkan Ekşioğlu
相关产品推荐
相关产品推荐

