You are not logged in.
The first time I promoted mORMot, I would have shared the points from the opening post. Later I thought, no matter what you say, many developers don't want to rethink. Today I promote mORMot with the slogan: It's on your computer in 3 minutes and removed without a trace in 3 seconds if you don't like it. Often I show concrete source code including the uses part. So it works with copy&paste.
I therefore tend to agree with @squirrel, but I'm not someone who knows anything about marketing.
With best regards
Thomas
I don't want to use "model", but for testing I added
CreateWithOwnModel([TAuthGroup, TAuthUser], {HandleUserAuthentication=} True);
like the example, but then I got errors:
You have to write:
CreateWithOwnModel([TAuthGroup, TFileAuthUser], {HandleUserAuthentication=} True, ROOT_NAME_FILE);
Compatibility with Delphi 7 should be possible. Remove the inline variables. You can rewrite the function u_ServiceUtils.CheckFileName with mORMot's own functions to get rid of TPath. You can replace TWebBrowser component with a TMemo. Then only text files are possible.
With best regards
Thomas
What is wrong with me??
The order of call is incorrect. You must call CreateMissingTables first and then CreateSqlMultiIndex. Look in unit test.orm.extdb.pas to see how it is implemented there. Or do it in the function InitializeTable of the ORM object.
With best regards
Thomas
That _Safe could return a read only fake DocVariantData is perhaps a little bit too much and contrary to the name „_safe“.
No. That is why you can write:
var
v: Variant;
isPeaPuree: Boolean;
begin
isPeaPuree := _Safe(v).O_['One'].O_['Two'].O_['Three'].B['IsPeaPuree'];
ShowMessage(IsPeaPuree.ToString(TUseBoolStrs.True));
You can easily reproduce the problem with the Init function. Switch on ReportMemoryLeaksOnShutdown. Write the following:
var
names: TRawUtf8DynArray;
begin
names := ['one', 'two'];
names := Nil;
Set a Breakpoint at names := Nil. When you have reached this point during debugging, press Ctrl+Alt+D and open the disassembly window. Now you can see that the compiler has automatically inserted a call to DynArrayClear for this Nil assignment. However, if you write Pointer(names) := Nil, this does not happen and the dynamic array can no longer be cleaned up at the end. In this case, FastMM will report the lost memory.
With best regards
Thomas
How should I call clear here? What would clear do with an empty []?
v.Clear;
TDocVariantData(v).Clear;
Set breakpoints as described in article under point 3. Then you can see what is going on.
With best regards
Thomas
However, I keep getting the error => TOrmTableDataSet3: Cannot modify a read-only dataset.
What is your question?
With best regards
Thomas
Can't find your article.
You must follow the link in the announcement. I write my posts in Delphi-Praxis Forum.
With best regards
Thomas
Regarding the servers, I realized that when I use TRestServerDB, the user or client can easily access the tables. Authentication might prevent this access, but from what I’ve studied, it doesn’t seem to check the access details. For example, imagine I have a "status" field that marks records as deleted (without physically deleting them). In the method for listing data, I don’t send these records, but since the client has access to the table through ORM, they can retrieve those records.
Also, when I use TRestServerFullMemory, I no longer have access to the ORM.
You don't read the answers to your questions carefully. You're making it difficult for everyone here to help you. You have already received useful links for the start in post #2. Have you studied this example, which only uses an HTTP server? Once you have found the entry point, you can search the mORMot source code for it and quickly find the answer to your questions. This example only uses a REST server and shows the use of the ORM. These two examples (here and here) show how REST and HTTP servers work together without using the ORM. Why should it be necessary to expose access to the ORM? Use TRestServerFullMemory as Main and only open access to the ORM in the interface-based services. You can combine everything and have already been shown how to do it here. Please let us help you too. Read through the tips you have received again.
With best regards
Thomas
As you can see in the code above, I pass the dbconnection to the InvoiceService constructor.
You must keep in mind that with the method you used to register the services, they are always sicShared. This is unimportant in a simple example, but you must take it into account for a server. But it is not necessary and with a little preparation you can implement it as shown in some examples.
Define your RestServer as follows:
type
TMainRestServer = class(TRestServerFullMemory)
strict private
FConnectionPool: TSqlDBOdbcConnectionProperties;
public
property ConnectionPool: TSqlDBOdbcConnectionProperties
read FConnectionPool;
CustomService as follows:
type
ICustomService = interface(IInvokable)
function GetConnection(out pmoConnection: TSqlDBConnection): Boolean;
end;
type
TCustomServiceObject = class(TInjectableObjectRest, ICustomService)
protected
function GetConnection(out pmoConnection: TSqlDBConnection): Boolean;
function TCustomServiceObject.GetConnection(out pmoConnection: TSqlDBConnection): Boolean;
begin
Result := False;
if TMainRestServer(Server).ConnectionPool <> Nil then
begin
pmoConnection := TMainRestServer(Server).ConnectionPool.ThreadSafeConnection;
Result := (pmoConnection <> Nil);
end;
end;
And use it in your services as follows:
var
json: RawUtf8;
dbConn: TSqlDBConnection;
dbStmt: ISqlDBStatement;
begin
if GetConnection(dbConn) then
begin
dbStmt := dbConn.NewStatementPrepared(...);
dbStmt.ExecutePreparedAndFetchAllAsJson(False, json);
With best regards
Thomas
mORMot2, Commit 7777, Delphi 12.1, 32Bit
Access violation for these combinations in function ObjArrayDelete().
...ObjArrayHelper.Delete(aIndex: Integer): Boolean;
var
Count: Integer;
begin
Count := Length(Self);
if (Count > 0)
and (aIndex >= 0)
and (aIndex < Count) then
begin
// Ok
ObjArrayDelete(Self, aIndex);
// Ok
ObjArrayDelete(Self, Self[aIndex]);
// raised exception class EAccessViolation with message 'Access violation at address 00FBCB6E. Read of address 70505408'.
ObjArrayDelete(Self, Count, Self[aIndex]);
// raised exception class EAccessViolation with message 'Access violation at address 00A2CB6E. Read of address 70505408'.
ObjArrayDelete(Self, aIndex, False, @Count);
With best regards
Thomas
I reviewed the mormot2 examples. In the examples, everyone uses the model.
Why is this a problem?
With best regards
Thomas
uses
mormot.core.base,
mormot.core.data,
mormot.core.os,
mormot.net.client;
var
content: RawByteString;
begin
content := HttpGet('https://tile.openstreetmap.org/10/549/339.png');
if content <> '' then
FileFromString(content, ChangeFileExt(ExeVersion.ProgramFileName, '.png'));
With best regards
Thomas
Unit: mormot.core.unicode, commit 7584
Function: function TrimLeft(const S: RawUtf8): RawUtf8;
There should be a +1 at the very end of the last line, otherwise one character too few will be returned.
if i = 1 then
result := S
else
FastSetString(result, @PByteArray(S)[i - 1], l - i + 1);
With best regards
Thomas
How to access a separate TRestServerDB instance?
You can find posts on this topic in this forum.
type
TCustomServiceObject = class(TInjectableObjectRest, ICustomService)
protected
function InternalAdd(const pmcItem: TOrmCustomRecord; pmForceID: Boolean = False): TID;
public
function GetDBDataRestServer: TRestServerDB; overload;
function GetDBBlobRestServer: TRestServerDB; overload;
end;
function TCustomServiceObject.InternalAdd(const pmcItem: TOrmCustomRecord; pmForceID: Boolean): TID;
var
restServer: TRestServerDB;
begin
restServer := GetDBDataRestServer;
if (restServer <> Nil) and (pmcItem <> Nil) then
Result := restServer.Server.Add(pmcItem, True, pmForceID)
else
Result := -1;
end;
function TCustomServiceObject.GetDBDataRestServer: TRestServerDB;
begin
with TRestAdminServer(Server) do
Result := RestServerPool.FindRestServer(GetDBDataRestServerID(ServiceRunningContext.Request.Session));
end;
function TCustomServiceObject.GetDBBlobRestServer: TRestServerDB;
begin
with TRestAdminServer(Server) do
Result := RestServerPool.FindRestServer(GetDBBlobRestServerID(ServiceRunningContext.Request.Session));
end;
With the technique shown above, you can also manage several separate databases for each organization without any problems. If you only want to use several databases, you do not need a pool and can access them directly. Then the following is enough:
type
TServerMain = class(TSynPersistent)
private
FHttpServer: TVGSHttpServer;
FDBDataRestServer: TDBDataRestServer;
FDBBlobRestServer: TDBBlobRestServer;
With best regards
Thomas
Anyway, I will revert it because it seems to be unneeded and unsafe - premature optimization or over optimization.
Which is the root of all evil, said.
Are you sure that's what you wanted?
else if (StartPos = 0) and
(Len = L) and
(PStrCnt(PAnsiChar(pointer(fDataString)) - _STRCNT)^ = 1) then
FastAssignUtf8(Text, fDataString) // fast return the fDataString instance
else
The description says: "fast return the fDataString instance", but the second part of the sentence says: "and set src to Nil". After calling function FastAssignUtf8, "fDataString" is empty. The function should then better called: GetAsTextAndEmptyStreamIfLenIsSize().
With best regards
Thomas
Commit 7267 tested and it works. Thank you very much and have a nice weekend.
With best regards
Thomas
I don't see when fThreads[] could be = 0, unless the log file input is corrupted / was not generated by TSynLog.
If remote logging is used (TRestHttpsClient.CreateForRemoteLogging and LogView), this may be the case.
With best regards
Thomas
mORMot2, Commit 7264 (fedb2ee), Delphi 12, 32Bit
Unit mormot.core.log
Line 7756 should be changed. It should be checked that (fThreads[Index] > 0), otherwise the call to function GetBitPtr could run into nothing.
function TSynLogFileView.Select(aRow: integer): integer;
...
for i := 0 to Count - 1 do
begin
if fLevels[i] in fEvents then
begin
if (fThreads = nil)
or GetBitPtr(pointer(fThreadSelected), fThreads[i] - 1) then // <-- here
begin
With best regards
Thomas
mORMot2, Commit 7243 (ee6c774), Delphi 12, 32Bit
Unit mormot.core.log
Line 7550 should be changed. The search should not be started from "fCount" but from "fSelectedCount".
function TSynLogFileView.SearchPreviousText(const aPattern: RawUtf8; aRow: integer): PtrInt;
...
// search from end
// for result := fCount - 1 downto aRow + 1 do <= This line should be changed
for result := fSelectedCount - 1 downto aRow + 1 do
if LineContains(aPattern, fSelected[result]) then
exit;
With best regards
Thomas
This is not clear for me, but will my possible solution "map" on DB field to one vaiants property?
Do Mormot fw have such a example? ORM implementation widely found in examples, but this - not.
You can read this article in Delphi-Praxis forum. It can also be found in the examples. To query this field directly via SQL, it uses the SQLite syntax.
With best regards
Thomas
I'm currently refactoring our code to use mORMot version 2 and I can't find the function CurrentServiceContext (previously found on the mORMot.pas unit) to get the TServiceRunningContext.
Unit mormot.rest.server
ServiceRunningContext, or better use TInjectableObjectRest for Interface-based Services.
With best regards
Thomas
I think, I don't seem to know how to use THttpMultiPartStream and THttpClientSocket.Post function.
Please give me some advice.
Did you watch the stream before sending it? The following simple test looks ok to me:
var
mpStream: THttpMultiPartStream;
fileStream: THandleStream;
begin
mpStream := THttpMultiPartStream.Create;
try
mpStream.AddContent('Field01', '100', TEXT_CONTENT_TYPE);
mpStream.AddContent('Field02', '{"Name": "Thomas"}', JSON_CONTENT_TYPE);
mpStream.AddFileContent('Data', 'data.json', '{"Name": "Thomas","Nickname": "tbo"}', JSON_CONTENT_TYPE, 'binary');
mpStream.AddFile('Test', MakePath([Executable.ProgramFilePath, 'test.json']));
mpStream.AddFile('Image', MakePath([Executable.ProgramFilePath, 'image.png']));
fileStream := TFileStreamEx.Create(MakePath([Executable.ProgramFilePath, 'mpTest.dat']), fmCreate);
try
mpStream.Flush;
StreamCopyUntilEnd(mpStream, fileStream);
finally
fileStream.Free;
end;
finally
mpStream.Free;
end;
With best regards
Thomas
Do you have more clue?
Sorry Arnaud, I made a stupid copy-paste mistake. Everything is fine with mORMot. The nasty thing was, the Delphi debugger totally misled me. I shouldn't work until the middle of the night. The only good thing was that I got some new ideas from all the debugging. Sorry again for the false alarm.
With best regards
Thomas
mORMot2, Commit 6984, Delphi XE, 32Bit
If the returned object has empty string fields, an AV occurs. Fields with text content have no problems.
Error message:
An exception of class EAccessViolation with message "Access violation at address 00604E98" has occurred. Reading address 0EFFFFF8 occurred.
The call stack is:
mormot.core.data.TRawUtf8Interning.Unique(???,'',0)
mormot.core.json._JL_RawUtf8($2ACEADC {''},$12EC84)
mormot.core.json._JL_RttiCustomProps('q',$12EC84)
mormot.core.json._JL_RttiCustom('q',$12EC84)
mormot.core.json._JL_RttiObjectWithID('¬ê¬'#2#$12#$F'P',$12EC84)
mormot.core.interfaces.TInterfaceMethodArgument.SetFromJson(...)
JSON looks like this:
'{RowID:0,ActiveState:0,CreatedAt:0,ModifiedAt:0,RecVersion:0,Username:"",Password:""},false]'
Function FastAssignNew in TRawUtf8Interning.Unique() fails:
procedure TRawUtf8Interning.Unique(
...
if (aText = nil) or
(aTextLen <= 0) then
FastAssignNew(aResult)
With values: aText = '' and aTextLen = 0
With best regards
Thomas
Do you know if there's a sample project available which shows the rest server pool usage ...
Sorry no, I have already written about this, but can't find the thread (I post in several forums). Perhaps you will find some inspiration in this thread. Another reader may have links at hand. The easiest way is to start from an example and incorporate the various techniques described. The help is written very detailed.
With best regards
Thomas
But in this scenario all functions would be inside a monolithic server process. As in a normal rest server implementation I could have several endpoints/routes but I could not replace only one part/route while keeping the rest of the other modules unchanged. I would always need to deploy a complete new server binary.
No, you can specify server URI and port and set up several Rest clients as required.
Generally: How you organize your data is up to you. You can have one or more databases for all customers, or one or more databases for each customer. The interfaces only do your routing and the necessary administration. What happens in the background afterwards is up to you. Example: Access via a pool of servers would be:
function TCustomServiceObject.GetReportRestOrm: IRestOrm;
begin
with TAdminRestServer(Server) do
Result := RestServerPool.FindReportRestServer(GetReportRestServerID(ServiceRunningContext.Request.Session)).Orm;
end;
function TReportService.UpdateSource(const pmcRowID: TID; const pmcSource: RawBlob): Boolean;
var
orm: IRestOrm;
begin
orm := GetReportRestOrm;
if orm <> Nil then
Result := orm.UpdateBlob(TOrmReport, pmcRowID, 'Source', pmcSource)
else
Result := False;
end;
Here you fetch the corresponding RestServer via a SessionID. The data can come from wherever you want. It can also be another service, created by you or by an outside service provider.
PS: My examples are simple to get started, but can also be easily expanded.
With best regards
Thomas
Thanks vor your reply, but how can i handle this, when I have a Setup Like this:
Myservice1.exe
Myservice2.exe
Myservice3.exe
Guiapp.exe
If you want to run multiple HttpServers, I see no advantage in this, you can instantiate the clients as follows:
TRestHttpClient.Create(ServerURI, TOrmModel.Create([], ROOT_NAME_SERVER), ServerPort, {Https=} (ServerPort = 443));
For an introduction to the topic, you can read articles Introduction to method-based services and Introduction to Interface-based Services in Delphi-Praxis forum.
With best regards
Thomas
Do I need to publish each service on a separate port on my PC, and directly connect my App which wants to fetch data from different services to each Service:Port individually?
You can register several services for one RestServer and one root name:
function TXRestServer.InitializeServices: Boolean;
begin
Result := (ServiceDefine(TX1Service, [IX1], sicSingle) <> Nil);
Result := Result and (ServiceDefine(TX2Service, [IX2], sicClientDriven) <> Nil);
You can register several RestServers for one HttpServer and port:
FHttpServer := TRestHttpServer.Create(pmcPort, [FXRestServer, FYRestServer], '+' {DomainName}, useHttpSocket);
Depending on the root name for a RestServer, it could look like this (example: root for X-RestServer is "store" and for Y-RestServer is "admin"):
domain.com/store
domain.com/admin
With best regards
Thomas
My problem is that with 500 child objects in the list, the whole process lasts 10 seconds for deserialisation. Is there a way to speed it up?
Something else is wrong. In this article you will find some benchmark values for comparison. The time for 500 objects should only be a few milliseconds.
With best regards
Thomas
Valid is true but the object isnt populated with any data.
Only "published" properties are serialized. If you want to serialize other properties, you must register your own functions with TRttiJson.RegisterCustomSerializer().
With best regards
Thomas
Delphi 12 Athens, mORMot2 V2.2
Following test case:
type
TTestItem = record
Name1: String;
Name2: String;
end;
var
item: TTestItem;
begin
item.Name1 := 'A';
item.Name2 := 'B';
ShowMessage(Utf8ToString(RecordSaveJson(item, TypeInfo(TTestItem))));
All tests except for the last one are ok. This leads to an access violation:
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChange('Name1', ''); // Ok: {"Name2":"B"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1'], ['']); // Ok: {"Name2":"B"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1', 'Name2'], ['N1', 'N2']); // Ok: {"N1":"A","N2":"B"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1', 'Name2'], ['N1', '']); // Ok: {"N1":"A"}
Rtti.ByTypeInfo[TypeInfo(TTestItem)].Props.NameChanges(['Name1', 'Name2'], ['', 'N2']); // Error: Access violation
With best regards
Thomas
I imagine the code should actually just modify the log filename, not the full path.
You are right. It should look like this:
procedure TServerLog.ComputeFileName;
begin
inherited ComputeFileName;
FFileName := MakePath([ExtractFilePath(FFileName), StringReplace(ExtractFileName(FFileName), ' ', '_', [rfReplaceAll])]);
end;
With best regards
Thomas
@Thomas: My problem with your demo is that I get this error:
First chance exception at $75ADF932. Exception class EOSException with message 'TFileStreamEx.Create(C:\Delphi\Components_Full\Database\mORMot2\ex\ThirdPartyDemos\tbo\04-HttpServer-InterfaceServices\bin\TestRestServer_20240102_175743.log) failed as ERROR_PATH_NOT_FOUND'. Process TestRestServer.exe (5340)
I have tested it with the following and it works for me without any problems:
Windows 10
Delphi 12 Athens
mORMot2 Version 2.2
Unfortunately, I can't help you.
With best regards
Thomas
Try to define your services BEFORE calling SetUser().
Are you sure? Then all my comments in source code since mORMot1 are wrong:
// IMPORTANT: First log in and then call ServiceDefine()!
if not FServerRestClient.SetUser(pmcAdminUsername, pmcAdminPassword, {HashedPassword=} False) then
Exit(scsErrLoginAdminUser); //=>
if not InitializeServices then
Exit(scsErrInitializeServices); //=>
end;
@RaelB: Have you already tried this example?
With best regards
Thomas
I have switched to the latest mORMot2 version. The following source code has worked so far. Two log files are now created. One for TSynLog and one for TVGServerLog.
type
TVGServerLog = class(TSynLog)
protected
procedure ComputeFileName; override;
end;
constructor TVGServerDaemon.Create;
begin
inherited Create(TVGServerSettings, ...
Settings.LogPath := TFileUtils.GetLogFileFolder(TVGServerSettings(Settings).ServerID);
Settings.SetLog(TVGServerLog);
Now TSynDaemon.AfterCreate must be overwritten. Name AfterCreate() does not really say what is being done. Perhaps a name like SetLogClassAfterCreate() would be more descriptive. If the LogClass has already been assigned in TSynDaemonSettings Create, it will be overwritten in AfterCreate(). Maybe check for Nil:
procedure TSynDaemon.AfterCreate;
begin
if RunFromSynTests then
fSettings.fLogClass := TSynLog // share the same TSynLog for all daemons
else if fSettings.LogClass = Nil then
fSettings.SetLog(TSynLog); // real world logging
end;
Best wishes and a Happy New Year 2024 to all.
With best regards
Thomas
Have any idea? Thank you.
I have created a simple unit for this:
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.UITypes, System.Classes,
mormot.core.base, mormot.core.text, mormot.core.rtti, mormot.core.log;
type
TRemoteLogging = class(TComponent)
strict private
FLogClass: TSynLogClass;
protected
const
DEFAULT_PORT = 8091;
public
destructor Destroy; override;
procedure Start(pmLogClass: TSynLogClass; const pmcUri: RawUtf8 = '127.0.0.1/LogService');
procedure Stop;
end;
implementation
uses
mormot.net.sock,
mormot.rest.http.client;
destructor TRemoteLogging.Destroy;
begin
Stop;
inherited Destroy;
end;
procedure TRemoteLogging.Start(pmLogClass: TSynLogClass; const pmcUri: RawUtf8);
var
uri: TUri;
begin
if pmLogClass <> Nil then
try
FLogClass := pmLogClass;
uri.From(pmcUri, Int32ToUtf8(DEFAULT_PORT));
TRestHttpsClient.CreateForRemoteLogging(uri.Server, pmLogClass, Utf8ToInteger(uri.Port), uri.Root);
except
on E: Exception do
pmLogClass.Add.Log(sllError, E);
end;
end;
procedure TRemoteLogging.Stop;
begin
if FLogClass <> Nil then
FLogClass.Family.EchoRemoteStop;
end;
It is used like this:
procedure TfrmMain.FormCreate(Sender: TObject);
begin
...
TRemoteLogging.Create(Self).Start(TSynLog);
With best regards
Thomas
If errmsg returns a Chinese string, the client displays it incorrectly, but if errmsg is of type string, the return is correct. I don't know why.
With this minimal information, I can only guess:
var s: String := Utf8ToString(errMsg);
With best regards
Thomas
Compile package mormot2 2.0.1: Exit code 1, Errors: 1, Hints: 2 ...
What could be wrong?
I think you are using a mORMot2 version that was created before May 2022. It would be better to update to the current one. That's all I can help with.
With best regards
Thomas
I am using this record to be able to copy data from one TOrmMyclass instance to an other by just copying the myclassdata record variable between them.
Why do you want to do it this way? Does not the following also meet your requirements:
type
TOrmItem = class(TOrm)
private
FField1: Integer;
FField2: Integer;
FField3: Integer;
published
property Field1: Integer
read FField1 write FField1;
property Field2: Integer
read FField2 write FField2;
property Field3: Integer
read FField3 write FField3;
end;
var
item1, item2: TOrmItem;
begin
item1 := TOrmItem.CreateWithID(High(Int64));
try
item1.Field1 := 1;
item1.Field2 := 2;
item1.Field3 := 3;
var json: RawJson := item1.GetJsonValues({Expand=} False, {WithID=} True, 'Field1,Field3');
item2 := TOrmItem.CreateWithID(1);
try
ShowMessage(item2.GetJsonValues(True, True, ALL_FIELDS));
item2.FillFrom(Pointer(json));
ShowMessage(item2.GetJsonValues(True, True, ALL_FIELDS));
finally
item2.Free;
end;
finally
item1.Free;
end;
The result is:
{"RowID":1,"Field1":0,"Field2":0,"Field3":0}
{"RowID":9223372036854775807,"Field1":1,"Field2":0,"Field3":3}
With best regards
Thomas
example-03:
in u_SharedTypes, the following line causes [dcc32 Error] u_SharedTypes.pas(75): E2034 Too many actual parameters
pmCheckedFileName^ := TPath.Combine(dirName, fileName, False)
Removing the ,false parameter will cause it to compile.
You are right, in Delphi 10.3 the definition still looks like this: "class function TPath.Combine(const Path1, Path2: string): string;".
With best regards
Thomas
The example-03 does not compile, ...
The example compiles for me without errors, warnings or hints. You do not specify which Delphi version you are using, nor which mORMot commit.
If the function name is expanded with a slash, the log shows following:
GET Files/GetAllFileNames/=400
mormot.rest.server.TRestServerRoutingRest { "errorCode":400, "errorText":"Invalid URI" }
The following is written in help:
function TServiceCalculator.Add(n1, n2: integer): integer;
will accept such requests:
- URL='root/Calculator.Add' and InBody='[ 1,2 ]'
- URL='root/Calculator.Add?+%5B+1%2C2+%5D' // decoded as ' [ 1,2 ]'
- URL='root/Calculator.Add?n1=1&n2=2' // in any order, even missing
If the help is correct, it is the expected result.
PS: If you put a breakpoint in function TRestServer.Uri() at position "node := fRouter.Lookup(ctxt);", you can trace it.
With best regards
Thomas
It was obvious that a TValuePUtf8Char.ToDouble method was missing.
I would lean towards the following:
type
TValuePUtf8Char = record
...
function ToFloat: TSynExtended; overload;
function ToFloat(const pmcDefaultValue: TSynExtended): TSynExtended; overload;
function TValuePUtf8Char.ToFloat: TSynExtended;
begin
Result := GetExtended(Text);
end;
function TValuePUtf8Char.ToFloat(const pmcDefaultValue: TSynExtended): TSynExtended;
var
err: Integer;
begin
Result := GetExtended(Text, err);
if err <> 0 then
Result := pmcDefaultValue;
end;
Then it can be written like this:
if pmvContext.ParseObject(['c', 'a', 'A'], @recValues) then
begin
rec.c := recValues[0].ToFloat;
rec.R1.a1 := recValues[1].ToFloat(-1);
rec.R1.a2 := recValues[2].ToFloat(NaN);
With best regards
Thomas
As you see that we split the JSON text to be a nested record, and would like to DynArrayLoadJson the JSON directly to the recs array.
This does not work automatically. Your definition must correspond to the JSON data format. But you can write and register your own serializer:
type
TTestRec = packed record
c: Double; // -> c
R1: record
a1: Double; // -> a
a2: Double; // -> A
end;
end;
PTestRec = ^TTestRec;
TTestRecArray = array of TTestRec;
TTestRecArrayFiler = class(TObject)
public
class procedure CustomReader(var pmvContext: TJsonParserContext; pmData: Pointer);
end;
class procedure TTestRecArrayFiler.CustomReader(var pmvContext: TJsonParserContext; pmData: Pointer);
var
rec: PTestRec absolute pmData;
recValues: array[0..2] of TValuePUtf8Char;
begin
if pmvContext.ParseObject(['c', 'a', 'A'], @recValues) then
begin
rec.c := GetExtended(recValues[0].Text);
rec.R1.a1 := GetExtended(recValues[1].Text);
rec.R1.a2 := GetExtended(recValues[2].Text);
end;
end;
const
JSON = '[{"c":"28403.81000000","a":"28420.61000000","A":"0.00351000"},{"c":"0.13690000","a":"0.13930000","A":"408.00000000"}]';
var
recs: TTestRecArray;
begin
if DynArrayLoadJson(recs, JSON, TypeInfo(TTestRecArray), Nil, True) then
begin
for var i: Integer := 0 to High(recs) do
ShowMessage(Format('c: %.5f, a: %.5f, A: %.5f', [recs[i].c, recs[i].R1.a1, recs[i].R1.a2]));
end;
initialization
TRttiJson.RegisterCustomSerializer(TypeInfo(TTestRec), TTestRecArrayFiler.CustomReader, Nil);
With best regards
Thomas