#1 2016-04-19 01:58:35

Eugene Ilyin
Member
From: milky_way/orion_arm/sun/earth
Registered: 2016-03-27
Posts: 132
Website

How assign TDocVariant property with TDynArray values?

Hi,

I didn't find in sources or documentation appropriate assignment routine or class method
for easy array of Type or T*DynArray assignment to TDocVariant properties with dvArray type.

Let's imagine, that  we received from TSQLRest instance RawUTF8 list of Names and another RawUTF8 list of Groups.

var
  Names: TRawUTF8DynArray;
  Groups: TRawUTF8DynArray;

Now we want to return this data to clients in JSON format like this one:

{"names":["ab","Eugene"],"groups":["G1","G2","G3","G4"]}

The most effective way will be _ObjFast() usage like this:

  Result := _ObjFast(['names', Names, 'groups', Groups]);

But this simple solution returns:

'{"names":null,"groups":null}'

May be it's because mORMor unable to do implicit conversion of T*DynArray's to TDocVariant properties with dvArray kind (TVariantDynArray).

So to build required JSON I have to move from this simple one line code to this solution:

var
  NamesDA: TDynArray; // dyn array wrapper must be declared before wrapped value
  Names: TRawUTF8DynArray;
  NamesCount: Integer;

  GroupsDA: TDynArray; // dyn array wrapper must be declared before wrapped value
  Groups: TRawUTF8DynArray;
  GroupCount: Integer;

  Content: Variant;
begin
  NamesDA.Init(TypeInfo(TRawUTF8DynArray), Names, @NamesCount);
  Names := TRawUTF8DynArrayFrom(['ab', 'Eugene']);
  NamesCount := 2;

  GroupsDA.Init(TypeInfo(TRawUTF8DynArray), Groups, @GroupCount);
  Groups := TRawUTF8DynArrayFrom(['G1', 'G2', 'G3', 'G4']);
  GroupCount := 4;

  TDocVariant.New(Content);
  Content.names := _JsonFast(NamesDA.SaveToJSON);
  Content.groups := _JsonFast(GroupsDA.SaveToJSON);

  Result := VariantSaveJSON(Content);
end;

As you see this is a much bigger then one line of code and required heavy _JsonFast / .SaveToJSON serialization/deserialization just for simple array assignment;

Question #1: Is a dyn array wrapper must be declared before a wrapped value?

Question #2: How to assign V.names with Names(or NamesDA) without intermediate JSON slow resource consuming serialization/deserialization?

Another way it to apply CoC principle and wrap result over record, but it looks heavy/synthetic for simple array to JSON assignment:

type
  TStub = packed record
    names: TRawUTF8DynArray;
    groups: TRawUTF8DynArray;
  end;
var
  Stub: TStub;
begin
  Stub.names := TRawUTF8DynArrayFrom(['ab', 'Eugene']);
  Stub.groups := TRawUTF8DynArrayFrom(['G1', 'G2', 'G3', 'G4']);
  Result := RecordSaveJSON(Stub, TypeInfo(TStub));
end;

Better, but not compact and ideal as expected and require fake stub record.

Question #3: In this example is record must be declared as packed?

Question #4: How get ideal _ObjFast(['names', Names, 'groups', Groups]); one line of code?

Thanks,

P.S.
Small off-topic: why it was decided not to create TBoolDynArray in SynCommons.pas(line 1030) in 1.18 ?
Is nobody never returns an array of Boolean to the clients?

Offline

#2 2016-04-19 07:06:33

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,182
Website

Re: How assign TDocVariant property with TDynArray values?

1. AFAIR no, you can define the wrapper anywhere, even have several wrappers for the same dynamic array.

2. You can use TDocVariantData.InitArrayFrom() to create a TDocVariantData directly from a TRawUTF8DynArray.
Or use _ArrFast().

3. If you use RecordSaveJSON(), the record should be packed (and defined as text for older version of Delphi, which do not have the extended RTTI).
But in your case, two dynamic arrays are two pointers, so "packed" does nothing special, since the two values creates directly an aligned record.
If you forget to set "packed" when you should have, an exception is raised.
Note that "packed" is not needed if you use RecordSave() binary format.

4. What is wrong with:

_ObjFast(['names', _ArrFast(['ab', 'Eugene']), 'groups', _ArrFast(['G1', 'G2', 'G3', 'G4'])])

If you need to work from TRawUTF8DynArray, you may write:

var names,groups: TRawUTF8DynArray;
    vnames,vgroups: variant;
begin
  names := TRawUTF8DynArrayFrom(['ab', 'Eugene']);
  groups := TRawUTF8DynArrayFrom(['G1', 'G2', 'G3', 'G4']);
  TDocVariantData(vnames).InitArrayFrom(names,JSON_OPTIONS_FAST);
  TDocVariantData(vgroups).InitArrayFrom(groups,JSON_OPTIONS_FAST);
  writeln(_ObjFast(['names', vnames, 'groups', vgroups]));

5. I've added TBooleanDynArray support.
See http://synopse.info/fossil/info/c835e3e929

Offline

#3 2016-04-19 09:55:48

Eugene Ilyin
Member
From: milky_way/orion_arm/sun/earth
Registered: 2016-03-27
Posts: 132
Website

Re: How assign TDocVariant property with TDynArray values?

Hi ab,

Thanks for fast replay,
Unfortunatelly _ObjFast(['names', _ArrFast(['ab', 'Eugene']) not appropriate because OneFieldValues() works with TRawUTF8DynArray, but not a static content.
Seem like usage of .InitArrayFrom is that what I need, but unfortunately it doesn't exist in 1.18 smile

I'm just playing with Auth* layer of framework and found that returning a simple list of logons in form of {'logons' : ['L1', 'L2', ...]} is not trivial.

Here is a tiny example:

  TTestServer = class(TSQLRestServerFullMemory)
    procedure Logons(Ctxt: TSQLRestServerURIContext);
  end;
procedure TTestServer.Logons(Ctxt: TSQLRestServerURIContext);
var
  Logons: TRawUTF8DynArray;
  Values: variant;
begin
  OneFieldValues(TSQLAuthUser, 'LogonName', '', Logons);
//  TDocVariantData(Values).InitArrayFrom(Logons, JSON_OPTIONS_FAST);
  Ctxt.ReturnsJson(_ObjFast(['logons', Values]));
end;
  FModel := TSQLModel.Create([]);
  FServer := TTestServer.Create(FModel, True);
  FServer.ServiceMethodByPassAuthentication('logons');
  FServer.CreateMissingTables;

I commented error line due to:
[dcc32 Error] TestServer.pas(26): E2003 Undeclared identifier: 'InitArrayFrom'

So, when I'm requesting /root/logons in browser, I'm expecting JSON logons in the form mentioned above.
Do you have any replacements for not existed InitArrayFrom?

Thanks

Offline

#4 2016-04-19 11:30:47

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,182
Website

Re: How assign TDocVariant property with TDynArray values?

Why use all those temporary Logons/Values variable?
Just create the JSON by hand using directly the JSON returned by TTestServer, as

Ctxt.Returns('{"logons":'+OneFieldValue(TSQLAuthUser,'LogonName','')+'}');

BTW, you may also take a look at TTextWriter.AddJSONArraysAsJSONObject() method and JSONObjectAsJSONArrays() function, which is able handle JSON arrays / object conversion with no intermediate storage.

Offline

#5 2016-04-19 12:15:19

Eugene Ilyin
Member
From: milky_way/orion_arm/sun/earth
Registered: 2016-03-27
Posts: 132
Website

Re: How assign TDocVariant property with TDynArray values?

I want to return all logons, not the first one: OneFieldValues, not OneFieldValue.
Where did you take all this magic methods .InitArrayFrom, .AddJSONArraysAsJSONObject(), JSONObjectAsJSONArrays(). They didn't exist in 1.18.

I don't insist on all this vars. I'm just trying to find simple, clear, transparent way to feed TDocVariant properties with TDynArrays.
One line solution will be perfect, but seems not possible.
Any TDynArray provided by requests to REST need some additional vars/DA's/magic to return this arrays as JSON names with JSON objects:
{"name1",[v1, v2, ...], "name2":[v1, v2, v3, ...]}.

Seems like this is most compact and fast/optimal way without creating stub records:

procedure TTestServer.Logons(Ctxt: TSQLRestServerURIContext);
var
  LogonsDA: TDynArray;
  Logons: TRawUTF8DynArray;
  Writer: TTextWriter;
begin
  if OneFieldValues(TSQLAuthUser, 'LogonName', '', Logons) then
  begin
    TAutoFree.One(Writer, TTextWriter.CreateOwnedStream);
    Writer.AddString('{"logons":');
    LogonsDA.Init(TypeInfo(TRawUTF8DynArray), Logons);
    Writer.AddDynArrayJSON(LogonsDA);
    Writer.AddString('}');
    Ctxt.Returns(Writer.Text);
  end;
end;

When you requesting /root/logons this method provides expected answer: {"logons":["Admin","Supervisor","User"]}

Hm... 3 vars + auto locker and 14 lines of code not so compact, transparent and clear as:

if OneFieldValues(TSQLAuthUser, 'LogonName', '', Logons) then
  Ctxt.Returns(['logons', Logons]);

Offline

#6 2016-04-19 16:49:24

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,182
Website

Re: How assign TDocVariant property with TDynArray values?

Please try the newly introduced TSQLRest.RetrieveOneFieldDocVariantArray.
See http://synopse.info/fossil/info/1e45013e0e

Offline

Board footer

Powered by FluxBB