Icon View Thread

The following is the text of the current message along with any replies.
Messages 11 to 20 of 24 total
Thread How do I parse JSON from the server?
Tue, Apr 21 2015 12:52 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Mark,

<< Is there anything in EWB2, perhaps based on that capabilities you've
outlined, that can populate the properties for a standard class from some
JSON? >>

Yes, what I just posted. Smile You simply declare the relevant properties as
published, make sure that your class descends from TPersistent, and then
fire up a TReader instance to load the JSON into the instance.

MyReader:=TReader.Create(MyJSONString);
Load(MyReader);

(The TPersistent.Load method is currently protected, so you'll have to do
this from within the class instance itself, but that's because there will be
a more user-friendly LoadFromString() method that does all of the TReader
handling for you).

I'm probably going to hook this into the TServerRequest functionality to
automate it even further at some point.

Tim Young
Elevate Software
www.elevatesoft.com





Thu, Apr 23 2015 6:48 AMPermanent Link

Mark Brooks

Slikware

Avatar

"Tim Young [Elevate Software]" wrote:

>>Yes, what I just posted. Smile You simply declare the relevant properties as
>>published, make sure that your class descends from TPersistent, and then
>>fire up a TReader instance to load the JSON into the instance.

Ok. So this continues to excite me. A lot. And not just for me, but for what this will enable new EWB users to achieve AND converts!

I have had a brief attempt at using this capability with our REST API. My new test classes (see below) read standard properties perfectly, however I cannot seem to handle embedded array properties. Can you shed any light please?

// ----------------------------------------------------------------
// Base Castrum Class to Simplify De-Serialisation
// ----------------------------------------------------------------

type
 TCastrumBase = class(TPersistent)
 public
   procedure LoadFromJSON(const AJSONString: string);
 end;

implementation

procedure TCastrumBase.LoadFromJSON(const AJSONString: string);
var
 R: TReader;
begin        
 R := TReader.Create(AJSONString);
 try      
   Self.Load(R);
 finally
   R.Free;
 end;
end;

// ---------------------------------------------------------------------------------------------
// Castrum Login Class To Hold Details Received From A Login Request
// ---------------------------------------------------------------------------------------------

type
 TCastrumLogin = class(TCastrumBase)
 private           
   fUserId: integer;
   fUserName: string;
   fName: string;
   fAvatarLink: string;
   fPreviousLoginDate: string;
   fSysAdmin: boolean;
   fUserAdmin: boolean;
   fWorkspaceAdmin: boolean;
 published
   property UserId: integer read fUserId write fUserId;
   property UserName: string read fUserName write fUserName;
   property Name: string read fName write fName;
   property AvatarLink: string read fAvatarLink write fAvatarLink;
 end;

// -------------------------------------------------------------------------------------------
// Castrum User Class To Hold Details Received From A User Request
// -------------------------------------------------------------------------------------------

type
 TCastrumUser = class(TCastrumBase)
 private
   fName: string;
 published
   property Name: string read fName write fName;
 end;

// -----------------------------------------------------------------------------------------
// Castrum Response Class To Hold Details From A Generic Request
// -----------------------------------------------------------------------------------------

type
 TCastrumResponse = class(TCastrumBase)
 private           
   fLogin: TCastrumLogin;
   fUsers: array of TCastrumUser;
   fAPIVersion: string;
   fMessage: string;
   fServerDate: string;
   fSuccess: boolean;
   fToken: string;
 public
   constructor Create; override;
   destructor Destroy; override;
 published
   property Login: TCastrumLogin read fLogin;  
   property Users: array of TCastrumUser read fUsers write fUsers;
   property APIVersion: string read fAPIVersion write fAPIVersion;
   property Message: string read fMessage write fMessage;
   property ServerDate: string read fServerDate write fServerDate;
   property Success: boolean read fSuccess write fSuccess;
   property Token: string read fToken write fToken;
 end;

implementation

constructor TCastrumResponse.Create;
begin
 inherited Create;
 fLogin := TCastrumLogin.Create;
end;

destructor TCastrumResponse.Destroy;
begin
 fLogin.Free;
 inherited Destroy;
end;

The Castrum Response class is used for all Castrum API calls. In the example shown, it caters for Login and Users calls. The remaining properties are common across all calls. The Login call populates the Login and generic properties correctly. The Users call does not populate the array property correctly.

I imagine that my issue is something to do with the way that I've defined the array in order for the internal TReader to process the JSON correctly.

Cheers
Mark




Thu, Apr 23 2015 7:22 AMPermanent Link

Matthew Jones

Mark Brooks wrote:

> Ok. So this continues to excite me. A lot. And not just for me, but
> for what this will enable new EWB users to achieve AND converts!

Hmm, it does raise interesting opportunities. My thought was that first
I'd do is make an application that has those objects, fill them in in
the Pascal code, and then do the "get JSON" call to see what form it
takes. Then you'd be able to see if you can alter the JSON text and
feed it back in. I would presume that to be the first step in
understanding how arrays are handled (assuming they are at the moment).

If they aren't, then it would be a useful exercise to help Tim to edit
the framework to make it. I did this with the existing parser, making
it fit more exotic situations like I managed to need. Tim didn't apply
directly IIRC, but the proof of concept and examples presumably helped.

Knowing Tim though, he has either already done it, or knows how to do
it better - EWB is very well thought through.

--

Matthew Jones
Thu, Apr 23 2015 3:26 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Mark,

<< I have had a brief attempt at using this capability with our REST API. My
new test classes (see below) read standard properties perfectly, however I
cannot seem to handle embedded array properties. Can you shed any light
please? >>

What you need to do is override the TPersistent.LoadProperty method to
handle array properties.  You can see how this is done with the TComponent
class:

function TComponent.LoadProperty(AReader: TReader): Boolean;
var
  TempPropertyName: String;
  TempClass: TClass;
  TempComponent: TComponent;
  TempName: String;
begin
  Result:=False;
  TempPropertyName:=AReader.GetPropertyName;
  if (TempPropertyName <> '') then
     begin
     if SameText(TempPropertyName,PERSISTENT_LOAD_CLASSNAME) then
        begin
        Result:=True;
        AReader.SkipPropertyName;
        AReader.SkipPropertySeparator;
        TempClass:=ClassByName(AReader.ReadString);
        if Assigned(TempClass) then
           begin
           TempComponent:=TComponentClass(TempClass).Create(TComponent(AReader.RootComponent));
           TempComponent.LoadProperties(AReader);
           end;
        end
     else if SameText(TempPropertyName,PERSISTENT_LOAD_NAME) then
        begin
        Result:=True;
        AReader.SkipPropertyName;
        AReader.SkipPropertySeparator;
        TempName:=AReader.ReadString;
        SetProperty(TempPropertyName,TempName);
        AReader.RootComponent.SetInstance(TempName,Self);
        end
     else if SameText(TempPropertyName,PERSISTENT_LOAD_PROPERTIES) then
        begin
        Result:=True;
        AReader.SkipPropertyName;
        AReader.SkipPropertySeparator;
        LoadObject(AReader);
        end
     else if SameText(TempPropertyName,PERSISTENT_LOAD_COMPONENTS) then
        begin
        Result:=True;
        AReader.SkipPropertyName;
        AReader.SkipPropertySeparator;
        LoadArray(AReader);
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  Here !!!
        end
     else
        Result:=inherited LoadProperty(AReader);
     end;
end;

Essentially, array properties must be handled via code, at least for now.  I
can't remember exactly what the issue was with having EWB automatically load
them, but it probably has something to do with not being able to get the
correct RTTI for such constructs.

Tim Young
Elevate Software
www.elevatesoft.com
Mon, Apr 27 2015 4:49 AMPermanent Link

Mark Brooks

Slikware

Avatar

"Tim Young [Elevate Software]" wrote:

>>What you need to do is override the TPersistent.LoadProperty method to
>>handle array properties.  You can see how this is done with the TComponent
>>class:

No worries Tim. Thought this was potentially too good to be true. Nonetheless, still a significant advance. Take care and good luck with the May release.
Mon, Apr 27 2015 5:44 PMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Mark,

<< Thought this was potentially too good to be true. >>

I certainly wouldn't characterize it that way.  It does exactly what you
want with almost zero code, and you don't need to do anything other than
what the code that I showed you is doing:

function TCastrumResponse.LoadProperty(AReader: TReader): Boolean;
var
  TempPropertyName: String;
begin
  Result:=False;
  TempPropertyName:=AReader.GetPropertyName;
  if (TempPropertyName <> '') then
     begin
     if SameText(TempPropertyName,'Users') then
        begin
        Result:=True;
        AReader.SkipPropertyName;
        AReader.SkipPropertySeparator;
        LoadArray(AReader);
        end
     else
        Result:=inherited LoadProperty(AReader);
     end;
end;

I also remembered the reason for the requirement of code for the arrays:
it's the bounds of the "list" that needs to be managed by the containing
object.  IOW, when it comes to things like lists, they can come in all sorts
of different forms.  Your example just happens to choose the most basic
example, which I'll most likely automate at some point.  Many times, lists
embedded in other components/controls can be string lists, object lists,
etc., and only the containing object knows the most efficient way to manage
setting the bounds of the list and adding items to the list.

<< Nonetheless, still a significant advance. Take care and good luck with
the May release. >>

Thanks. Smile

Tim Young
Elevate Software
www.elevatesoft.com
Tue, Apr 28 2015 4:07 PMPermanent Link

Mark Brooks

Slikware

Avatar

"Tim Young [Elevate Software]" wrote:

>>I certainly wouldn't characterize it that way.  It does exactly what you
>>want with almost zero code, and you don't need to do anything other than
>>what the code that I showed you is doing:

Point taken Tim. Guess I just want the moon on a stick!

I will play further.

Thanks
Wed, Apr 29 2015 6:41 AMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Mark,

Of course, as soon as I tell you that, I realize that what I said isn't
entirely correct.  You'll also need to override this TPersistent method:

        function LoadArrayElement(AReader: TReader): Boolean; virtual;

in your classes that have array properties.  It can just look like this:

function TCastrumResponse.LoadArrayElement(AReader: TReader): Boolean;
begin
  // Here you need to put the code for expanding the array, as necessary
   Result:=inherited LoadArrayElement(AReader: TReader);
end;

The ancestor TPersistent.LoadArrayElement method already knows how to load
any TPersistent descendant classes, so that part is all set and it will be
able to take it from there in terms of loading each TCastrumUser instance.

Tim Young
Elevate Software
www.elevatesoft.com




Fri, May 1 2015 6:22 AMPermanent Link

Mark Brooks

Slikware

Avatar

"Tim Young [Elevate Software]" wrote:

>>function TCastrumResponse.LoadArrayElement(AReader: TReader): Boolean;
>>begin
>>   // Here you need to put the code for expanding the array, as necessary
>>    Result:=inherited LoadArrayElement(AReader: TReader);
>>end;

>>The ancestor TPersistent.LoadArrayElement method already knows how to load
>>any TPersistent descendant classes, so that part is all set and it will be
>>able to take it from there in terms of loading each TCastrumUser instance.

Sorry Tim. I must be having a bad week, but I am just not getting this? I don't understand the parsing process that takes place when the TPersistent descendent parses array properties and therefore don't understand where to "intercept" and how to allocate appropriate storage at that point. Embarrassed somewhat.
Fri, May 1 2015 7:52 AMPermanent Link

Tim Young [Elevate Software]

Elevate Software, Inc.

Avatar

Email timyoung@elevatesoft.com

Mark,

<< Sorry Tim. I must be having a bad week, but I am just not getting this? I
don't understand the parsing process that takes place when the TPersistent
descendent parses array properties and therefore don't understand where to
"intercept" and how to allocate appropriate storage at that point.
Embarrassed somewhat. >>

Actually, I left out a little bit still (hard keeping tabs on things this
week...), so it's not surprising that it's hard to understand.

It works like this:

1) You start to load your TCastrumResponse class using the TPersistent.Load
method.
2) This starts the process of EWB looping through your published properties,
loading each one using the LoadProperty method.
3) Since you overrode the LoadProperty method for the TCastrumResponse
class, it will get called for each property.
4) Once the LoadProperty method is called for the Users property, your
LoadProperty method will *not* call the inherited LoadProperty, but rather
will call the LoadArray method.  This will start parsing of the array of
TCastrumUser instances.
5) Because you overrode the LoadArrayElement method for the TCastrumResponse
class, it will get called for each array element.
6) In the LoadArrayElement method, you need to expand the TCastrumUser array
by one, create a new TCastrumUser instance, and then load it:

function TCastrumResponse.LoadArrayElement(AReader: TReader): Boolean;
var
  TempLength: Integer;
begin
  TempLength:=Length(FUsers);
  Inc(TempLength);
  SetLength(FUsers,TempLength);
  FUsers[TempLength]:=TCastrumUser.Create;
  FUsers[TempLength].LoadObject(AReader);
end;

This last bit is the part that I left out.  Previously I had it calling the
inherited LoadArrayElement, which isn't correct.

If you need to load more than one type of array for a given class, then
you'll simply want to save the property name to a class variable in the
LoadProperty method, and then use that property name here to determine which
property array you're loading:

function TCastrumResponse.LoadProperty(AReader: TReader): Boolean;
begin
 Result:=False;
 FPropertyName:=AReader.GetPropertyName;   //  FPropertyName: String is the
class variable to add
 if (FPropertyName <> '') then
    begin
    if SameText(FPropertyName,'Users') or
SameText(FPropertyName,'SomeOtherArray') then
       begin
       Result:=True;
       AReader.SkipPropertyName;
       AReader.SkipPropertySeparator;
       LoadArray(AReader);
       end
    else
       Result:=inherited LoadProperty(AReader);
    end;
end;

function TCastrumResponse.LoadArrayElement(AReader: TReader): Boolean;
var
  TempLength: Integer;
begin
  if SameText(FPropertyName,'Users') then
     begin
     TempLength:=Length(FUsers);
     Inc(TempLength);
     SetLength(FUsers,TempLength);
     FUsers[TempLength]:=TCastrumUser.Create;
     FUsers[TempLength].LoadObject(AReader);
     end
  else if SameText(FPropertyName,'SomeOtherArray') then
    .......
end;

Tim Young
Elevate Software
www.elevatesoft.com
« Previous PagePage 2 of 3Next Page »
Jump to Page:  1 2 3
Image