Recent

Author Topic: Memory leak on JSON array processing  (Read 293 times)

ldvmikro

  • Newbie
  • Posts: 4
Memory leak on JSON array processing
« on: April 24, 2025, 01:54:56 pm »
Hello,
I have this JSon:

Code: Text  [Select][+][-]
  1. [
  2.     {
  3.         "id": "1",
  4.         "name": "Area 1",
  5.         "regions": [
  6.             {
  7.                 "id": "1.1",
  8.                 "zoneId": "1",
  9.                 "name": "Espacio 1",
  10.                 "zoneDescription": "Max=10009;Total=6200"
  11.             },
  12.             {
  13.                 "id": "1.2",
  14.                 "zoneId": "1",
  15.                 "name": "Espacio 2",
  16.                 "zoneDescription": "Max=10000;Total=0"
  17.             },
  18.             {
  19.                 "id": "1.3",
  20.                 "zoneId": "1",
  21.                 "name": "Espacio 3",
  22.                 "zoneDescription": "Max=10000;Total=-3222"
  23.             }
  24.         ]
  25.     },
  26.     {
  27.         "id": "2",
  28.         "name": "Area 2",
  29.         "regions": [
  30.             {
  31.                 "id": "2.1",
  32.                 "zoneId": "2",
  33.                 "name": "Espacio 4",
  34.                 "zoneDescription": "Max=10000;Total=0"
  35.             },
  36.             {
  37.                 "id": "2.2",
  38.                 "zoneId": "2",
  39.                 "name": "Espacio 5",
  40.                 "zoneDescription": "Max=10000;Total=0"
  41.             }
  42.         ]
  43.     }
  44.   ]
  45.  

and code for process it:
Code: Pascal  [Select][+][-]
  1. uses
  2.   Classes, SysUtils, fpjson, jsonparser;
  3.  
  4. procedure ParseZonesJSON(const AJsonString: string);
  5. var
  6.   JSONData: TJSONData;
  7.   ZonesArray: TJSONArray;
  8.   ZoneObject, RegionObject: TJSONObject;
  9.   ZoneItem, RegionItem: TJSONEnum;
  10.   ZoneId, ZoneName, RegionId, RegionName, ZoneDesc, ZoneRegionId: string;
  11. begin
  12.   try
  13.     //JSON-line parsing
  14.     JSONData := GetJSON(AJsonString);
  15.  
  16.     // Checking that its array
  17.     if not (JSONData is TJSONArray) then
  18.     begin
  19.       WriteLn('Error: Waitng JSON-array.');
  20.       Exit;
  21.     end;
  22.  
  23.     ZonesArray := TJSONArray(JSONData);
  24.  
  25.     // Reviewing all areas
  26.     for ZoneItem in ZonesArray do
  27.     begin
  28.       ZoneObject := TJSONObject(ZoneItem.Value);
  29.  
  30.       // Get data for
  31.       ZoneId := ZoneObject.Get('id', '');
  32.       ZoneName := ZoneObject.Get('name', '');
  33.  
  34.       WriteLn('Zone ID: ', ZoneId);
  35.       WriteLn('Zone Name: ', ZoneName);
  36.       WriteLn('Regions:');
  37.  
  38.       // Get an array of regions
  39.       if ZoneObject.Find('regions', JSONData) and (JSONData is TJSONArray) then
  40.       begin
  41.         for RegionItem in TJSONArray(JSONData) do
  42.         begin
  43.           RegionObject := TJSONObject(RegionItem.Value);
  44.  
  45.           // Extracting region data
  46.           RegionId := RegionObject.Get('id', '');
  47.           RegionName := RegionObject.Get('name', '');
  48.           ZoneDesc := RegionObject.Get('zoneDescription', '');
  49.           ZoneRegionId := RegionObject.Get('zoneId', '');
  50.  
  51.           WriteLn('  - Region ID: ', RegionId);
  52.           WriteLn('    Zone ID: ', ZoneRegionId);
  53.           WriteLn('    Name: ', RegionName);
  54.           WriteLn('    Description: ', ZoneDesc);
  55.         end;
  56.       end;
  57.       WriteLn('------------------');
  58.     end;
  59.   finally
  60.     if Assigned(JSONData) then
  61.       JSONData.Free; // Freeing up the memory
  62.   end;
  63. end;
  64.  

The code works but if I don't comment this block,
Code: Pascal  [Select][+][-]
  1.  if ZoneObject.Find('regions', JSONData) and (JSONData is TJSONArray) then
  2.       begin
  3.         for RegionItem in TJSONArray(JSONData) do
  4.         begin
  5.           RegionObject := TJSONObject(RegionItem.Value);
  6.  
  7.           // Extracting region data
  8.           RegionId := RegionObject.Get('id', '');
  9.           RegionName := RegionObject.Get('name', '');
  10.           ZoneDesc := RegionObject.Get('zoneDescription', '');
  11.           ZoneRegionId := RegionObject.Get('zoneId', '');
  12.  
  13.           WriteLn('  - Region ID: ', RegionId);
  14.           WriteLn('    Zone ID: ', ZoneRegionId);
  15.           WriteLn('    Name: ', RegionName);
  16.           WriteLn('    Description: ', ZoneDesc);
  17.         end;
  18.       end;
  19.       WriteLn('------------------');
  20.     end;
  21.  

this line in particular,
Code: Pascal  [Select][+][-]
  1.  if ZoneObject.Find('regions', JSONData) and (JSONData is TJSONArray) then
  2.  

the RAM consumption goes up until the app won't close.
Any idea what is the reason?
Thanks.

paweld

  • Hero Member
  • *****
  • Posts: 1363
Re: Memory leak on JSON array processing
« Reply #1 on: April 24, 2025, 02:14:53 pm »
In this line:
Code: Pascal  [Select][+][-]
  1. if ZoneObject.Find('regions', JSONData) and (JSONData is TJSONArray) then
you are replacing the JSONData variable with a new class, not releasing the previous instance.
all you need to do is add a new variable for the purpose of this loop:
Code: Pascal  [Select][+][-]
  1. procedure ParseZonesJSON(const AJsonString: string);
  2. var
  3.   JSONData, jsondata2: TJSONData;
  4.   ZonesArray: TJSONArray;
  5.   ZoneObject, RegionObject: TJSONObject;
  6.   ZoneItem, RegionItem: TJSONEnum;
  7.   ZoneId, ZoneName, RegionId, RegionName, ZoneDesc, ZoneRegionId: string;
  8. begin
  9.   try
  10.     //JSON-line parsing
  11.     JSONData := GetJSON(AJsonString);
  12.  
  13.     // Checking that its array
  14.     if not (JSONData is TJSONArray) then
  15.     begin
  16.       WriteLn('Error: Waitng JSON-array.');
  17.       Exit;
  18.     end;
  19.  
  20.     ZonesArray := TJSONArray(JSONData);
  21.  
  22.     // Reviewing all areas
  23.     for ZoneItem in ZonesArray do
  24.     begin
  25.       ZoneObject := TJSONObject(ZoneItem.Value);
  26.  
  27.       // Get data for
  28.       ZoneId := ZoneObject.Get('id', '');
  29.       ZoneName := ZoneObject.Get('name', '');
  30.  
  31.       WriteLn('Zone ID: ', ZoneId);
  32.       WriteLn('Zone Name: ', ZoneName);
  33.       WriteLn('Regions:');
  34.  
  35.       // Get an array of regions
  36.       if ZoneObject.Find('regions', jsondata2) and (jsondata2 is TJSONArray) then
  37.       begin
  38.         for RegionItem in TJSONArray(jsondata2) do
  39.         begin
  40.           RegionObject := TJSONObject(RegionItem.Value);
  41.  
  42.           // Extracting region data
  43.           RegionId := RegionObject.Get('id', '');
  44.           RegionName := RegionObject.Get('name', '');
  45.           ZoneDesc := RegionObject.Get('zoneDescription', '');
  46.           ZoneRegionId := RegionObject.Get('zoneId', '');
  47.  
  48.           WriteLn('  - Region ID: ', RegionId);
  49.           WriteLn('    Zone ID: ', ZoneRegionId);
  50.           WriteLn('    Name: ', RegionName);
  51.           WriteLn('    Description: ', ZoneDesc);
  52.         end;
  53.       end;
  54.       WriteLn('------------------');
  55.     end;
  56.   finally
  57.     if Assigned(JSONData) then
  58.       JSONData.Free; // Freeing up the memory
  59.   end;
  60. end;
Best regards / Pozdrawiam
paweld

Khrys

  • Full Member
  • ***
  • Posts: 227
Re: Memory leak on JSON array processing
« Reply #2 on: April 24, 2025, 04:12:29 pm »
In addition to what @paweld said, I'd also recommend initializing  JSONData  to  Nil  before calling  GetJSON.

This is because  GetJSON  is a regular function and not a constructor, meaning that if an exception is raised (such as when the input JSON is malformed), no destructor runs and the result is never assigned, which will lead to the  finally  block (probably) calling  Free  on an uninitialized reference.

(By the way, checking  Assigned  before calling  Free  is redundant;  Free  already does that for you.)

Another peculiarity of  GetJSON  is that it may simply return  Nil  without throwing an exception if the input string is empty. You've already got this case covered by checking  is TJSONArray,  but it's another pitfall one needs to be wary of.  ::)



To recap, this is the minimal skeleton I'd recommend for parsing JSON:

Code: Pascal  [Select][+][-]
  1. procedure ExampleParseJSON(const Source: String);
  2. var
  3.   Data: TJSONData = Nil;
  4. begin
  5.   try
  6.     Data := GetJSON(Source);
  7.     if Data = Nil then raise Exception.Create('JSON source is empty'); // Alternatively you could call Exit()
  8.     // Do actual processing [...]
  9.   finally
  10.     Data.Free();
  11.   end;
  12. end;

ldvmikro

  • Newbie
  • Posts: 4
Re: Memory leak on JSON array processing
« Reply #3 on: April 24, 2025, 04:46:13 pm »
By modifying the code according to the comments, everything works perfectly. I appreciate your great help in solving my problem. Thank you @paweld and @Khrys for your immediate help.

 

TinyPortal © 2005-2018