Fill local array pointing to field of derived class, but later field empty

200 views Asked by At

I must be overlooking something in my web service...

Data structures:

TResIDNameRec = record
   id  : Integer;
   name: String;
end;

TResIDNameArr = array of TResIDNameRec;

TResBase = class(TJSONStructure)  // just a class(TObject)
private
public
   &type       : String;
   success     : Integer;
   errormessage: String;
   threadid    : Integer;
   constructor Create; overload;       // empty
   constructor Create(ADescendantClassName: String); overload;  // sets &type
end;

TResClass = class of TResBase;

TResGetList = class(TResBase)
private
public
   activities, 
   projects,   
   customers: TResIDNameArr;
   constructor Create; overload;    // inherited Create(Self.ClassName);
end;

and then a

type
  TWebAct = (
   ttlogin,
   ...
   ttgetlist,
   ...
   ttgetversion
   );

const
  cWebActStructures: Array[TWebAct] of
  record
     RequestClass : TReqClass;
     ResponseClass: TResClass;
  end
  = (
     { ttlogin     } (RequestClass: TReqLogin;      ResponseClass: TResLogin;),
     ...
     { ttgetlist   } (RequestClass: TReqGetList;    ResponseClass: TResGetList;),
     ...
     { ttgetversion} (RequestClass: TReqGetVersion; ResponseClass: TResGetVersion;)
    );

I have a private field on my WebModule:

FResponse: TResBase;     // Response object

In the WebmoduleBeforeDispatch, FWebAct is a ttgetlist, and I do:

lResponseClass := cWebActStructures[FWebAct].ResponseClass;
FResponse      := lResponseClass.Create(lResponseClass.ClassName);  
// FResponse.ClassName is TResGetList, OK

In the OnAction handler for the TWebActionItem that handles the 'ttgetlist', here is the essential part:

var   
   lListArr : TResIDNameArr;
begin   
   lListArr := (FResponse as TResGetList).activities;

   with AClientDataSet do  
   begin
      Open;
      SetLength(lListArr,RecordCount);  // Pre-allocate (currently 152)
      l := 0;
      First;
      while not EOF do
      begin
         if SomeConditionMet then
         begin
            lListArr[l].id   := FieldByName(lIDVeld).AsInteger;
            lListArr[l].name := FieldByName(SName).AsString;
            Inc(l);
         end;
         Next;
      end;
      Close;
      SetLength(lListArr,l+1);  // Adjust (l is now 108)
    end;
    FResponse.success := 1;

lListArr shows correct data at this moment, object inspector shows:

((3, 'Activiteit 3'), (4, 'Activiteit 3.1'), (5, 'Activiteit 3.2'), (10, 'Activiteit 3.3'), (8, 'Activiteit 5'),...

In WebmoduleAfterDispatch:

var lJSO: ISuperObject;
begin
   lJSO := FResponse.ToJson;

LJSO.AsString does have the correct fields (&type, success, ... activities, ... ), and e.g. success=1, but activities is empty:

{
"errormessage":"",
"success":1,
"projects":[],
"threadid":1556,
"type":"ttgetlistresult",
"customers":[],
"activities":[]
}

What am I overlooking? Is maybe the lListArr := (FResponse as TResGetList).activities in the OnAction handler 'not good enough'?

BTW Feel free to update the question title, this was the best I could come up with.

1

There are 1 answers

0
Rob Kennedy On BEST ANSWER

When you assign lListArr, the local variable receives a reference to the same array that the activities field references. However, when you call SetLength(lListArr, ...), that makes lListArr refer to a new array, completely separate from the one it referenced before.

If you want the field to refer to the same array, you need to assign it back to the field:

(FResponse as TResGetList).activities := lListArr;

You can make that assignment at any time after the length has been set. Changes the the contents of the array will be visible through either variable. Changing the length is not the same as changing the contents, though. Changing the length re-allocates the entire array and gives a new, unique array.