Search code examples
jsondelphidelphi-xe5

How do I get a dynamic TClientDataset (based on TRESTResponseDataSetAdapter) to correctly recognise boolean values?


I've built a delphi client sample in Delphi XE5 that utilises the Rest library:

  • TRestClient
  • TRestRequest
  • TRestResponse
  • TRESTResponseDataSetAdapter
  • TClientDataSet (and TDataSource bound to TDBGrid and
    TcxGridDBTableView)

All served by an ASP.net Web API that generates a dataset based on dynamic queries from Delphi. I can successfully run the queries and return data to the client and render the data, however boolean fields are rendered blank.

  • How do I get TClientDataSet to recognise the data types correctly?
  • Is this the proper way to use the TRESTResponseDataSetAdapter or TClientDataSet?
  • Does anyone have any experience with these components

EDIT Surprisingly, Embarcadero's TRestResponseDatasetAdapter does this internally to create fields:

procedure TCustomJSONDataSetAdapter.CB_CollectFieldDefs(const AJSONObject: TJSONObject);
var
  LJSONPair: TJSONPair;
begin
  for LJSONPair in AJSONObject do
  begin
    DoAddDataSetFieldDef(LJSONPair.JsonString.Value, ftString);
  end;
end;

The field types are all hardcoded to ftString!

This is the returned json:

"Table": [
        {
            "Id": 34,
            "Node": "Navision_ASN_1",
            "FormatId": 2,
            "Value": null,
            "ParentID": null,
            "DocumentOrder": 1,
            "Combined": false,
            "Delimiter": "",
            "ValueType": 0,
            "Ignore": false,
            "IsIndexer": false,
            "IsCounter": false,
            "StringFormat": null
        },
        {
            "Id": 35,
            "Node": "MessageHeader",
            "FormatId": 2,
            "Value": null,
            "ParentID": 34,
            "DocumentOrder": 2,
            "Combined": false,
            "Delimiter": "",
            "ValueType": 0,
            "Ignore": false,
            "IsIndexer": false,
            "IsCounter": false,
            "StringFormat": null
        },
        {
            "Id": 52,
            "Node": "Consignment",
            "FormatId": 2,
            "Value": null,
            "ParentID": 34,
            "DocumentOrder": 13,
            "Combined": false,
            "Delimiter": "",
            "ValueType": 0,
            "Ignore": false,
            "IsIndexer": false,
            "IsCounter": false,
            "StringFormat": null
        },
        {
            "Id": 53,
            "Node": "Line",
            "FormatId": 2,
            "Value": null,
            "ParentID": 52,
            "DocumentOrder": 18,
            "Combined": false,
            "Delimiter": "",
            "ValueType": 0,
            "Ignore": false,
            "IsIndexer": false,
            "IsCounter": false,
            "StringFormat": null
        }
    ]

Solution

  • You can't!

    The issue comes down to the TJsonObject class from Embarcadero Delphi. In the Rest.Response.Adapter unit the json returned is parsed into a TJsonObject, and then all the TJsonPairs in the object are iterated over in CB_CollectFieldDefs and CB_CollectFieldData.

    First problem: CB_CollectionFieldDefs hardcodes ftString as the data type to add to the dataSet for each field definition. So I returned the field types from my Web API call to remove the guesswork and built the field definitions manually. When the RestResponseDataSetAdapter has field definitions, the unit correctly skips CB_CollectionFieldDefs, this leads to the second problem.

    Second problem: the TjsonPair in the object does not correctly parse boolean json values. By the time CB_CollectFieldData is called, all the boolean values which should be variant types are empty strings. So we get the exception 'Could not convert variant of type (UnicodeString) into type (Boolean)'.

    I have sadly had to drop this promising set of components in favour of enhancing Fabricio Colombo's excellent rest client api. Using the pattern in his GetAsDataSet logic, I created a number of post methods to post json (since the json creation from built in delphi units were incorrect, I substituted with SuperObject). I introduced PostJson and CreateDataset which both return TClientDataSets.

    function TResource.CreateDataset(data: string; table: string = ''; titles: string = ''): TClientDataSet;
    var
      vJson: ISuperObject;
    begin
      vJson := SuperObject.SO(data);
    
      Result := TJsonToDataSetConverter.CreateDataSetMetadata(vJson, table, titles);
      TJsonToDataSetConverter.ToDataSet(Result, vJson.O[table]);
    end;
    // which allows me to do:
    ds := jsonrestclient1.Resource(url)
              .ContentType(RestUtils.MediaType_Json)
              .Accept(restutils.MediaType_Json)
              .PostJson(json, 'Table', 'Titles');
    // or
    rs := jsonrestclient1.Resource(url)
              .ContentType(RestUtils.MediaType_Json)
              .Accept(restutils.MediaType_Json);
    data:= rs.PostJson(Query) ;
    vJson := SO(data);
    fHasMore := vJson.B['HasMore'];
    ds:= rs.CreateDataset(data, 'Table', 'Titles');