#1 2014-08-03 12:45:48

cheemeng
Member
From: Malaysia
Registered: 2011-08-09
Posts: 61

Switching Response Format (XML/JSON)

I understand ab's intentions in using JSON (speed, memory, etc), however there are client applications that can only process XML response.

In .Net's WebAPI framework, the response serializer automatically switches depending on the http Accept parameter (application/json, application/xml, text/json, text/xml).

Is there a way that we could do this in mORMot? Perhaps registering a new serializer/deserializer class? Or is returning TServiceCustomAnswer the only viable solution?

Thanks!

Offline

#2 2014-08-03 13:48:53

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

Re: Switching Response Format (XML/JSON)

JSON is implemented as transmission layer from the ground up (with UTF-8 encoding for text).
The the SAD 1.18 pdf about this choice.
For both ORM and SOA, the JSON content is generated directly by lowest-level of code.
So you can not "switch" from XML to JSON by changing a class.

But, as you stated, by using TServiceCustomAnswer you can return any content, including XML formatted documents.

Why on earth would you like to use XML today, if it is only use to transmit the same information as JSON?
Even a JSON parser and emitter would be easier to implement than a XML to JSON / JSON to XML translator.
Which kind of client applications can only process XML?
wink

Offline

#3 2014-08-03 14:55:41

cheemeng
Member
From: Malaysia
Registered: 2011-08-09
Posts: 61

Re: Switching Response Format (XML/JSON)

The kinds written in Java by people in the early 2000s.

Arnaud, does this mean there is no way to implement a 'switch' for response type?

Or perhaps I should create a helper function which looks for the Http Accept parameter if it exists, and return the response type accordingly? e.g.

function ReturnContent(Ctx, Model): TServiceCustomAnswer;
begin
  if AcceptParameter = 'xml' then
    Result.Content := XMLify(Model)
  else Result.Content := JSONify(Model);
end;

function TServiceDevice.GetStatus(const IP: string): TServiceCustomAnswer;
begin
  Model := GetDeviceStatus(IP);
  Result := ReturnContent(Ctx, Model);
end;

Or should I create a subclass of TServiceContainerClient to perform some 'magic'?

PS: By the way how do I 'subscribe' to a thread so that I get notified when someone replies? For instance even though I started this thread, I didn't get any e-mail notifications when you posted your reply.

Offline

#4 2014-08-04 08:57:48

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

Re: Switching Response Format (XML/JSON)

AFAIK Java does handle pretty well JSON content, even in old versions of the JVM.

XML to/from JSON conversion can be a bit non obvious.
For instance, the first online converters shown by Google are not able to convert the following JSON consistently:

[1,2,3]

Some converters fails to convert it, others generate <0>1</0><1>2</1><2>3</2>...
We will follow the 2nd pattern.

I found out that some of the converters are not able to convert back the result of their own conversion!
sad
We will try to do better.
smile
Yes, I'm currently adding XMLToJSON and JSONToXML functions to SynCommons.pas.

See newly added JSONToXML() JSONBufferToXML() and TTextWriter.JSONBufferToXML():
http://synopse.info/fossil/info/60541ac2a0ad

Then, I suspect it may be possible to put a global flag for TSQLRestServer, then let TSQLRestServer.URI() inspect the HTTP "Accept" header, and if the client does only accept XML and not JSON, convert JSON into XML directly.
In this case, JSONBufferToXML() will do the conversion in one step.

Offline

#5 2014-08-04 12:26:21

cheemeng
Member
From: Malaysia
Registered: 2011-08-09
Posts: 61

Re: Switching Response Format (XML/JSON)

Thanks Arnaud! big_smile

Sometimes certain client applications are not within our control, and the vendor that did the original application insisted on using XML and not changing it.

Last edited by cheemeng (2014-08-04 12:57:15)

Offline

#6 2014-08-04 14:13:06

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

Re: Switching Response Format (XML/JSON)

Interface-based services can now return the result value as XML object instead of JSON array or object if TServiceFactoryServer.ResultAsJSONObject is set.
It can be useful e.g. when consuming services from XML only clients...
As an alternative, TServiceFactoryServer.ResultAsXMLObjectIfAcceptOnlyXML option will recognize 'Accept: application/xml' HTTP header (exact match) and return XML instead of JSON in this case

See http://synopse.info/fossil/info/5a89b5e46f

In conjunction with URI-encoded parameters - available with TSQLRestRoutingREST default routing scheme - it could be an useful alternative.
You can specify the input parameters named at the URI level, then let the server return XML content, as expected by your client.

Up to now, this XML alternative response type can NOT be consumed by mORMot.pas - so TServiceFactoryServer.ResultAsXMLObjectIfAcceptOnlyXML option should be used if you want the service to be accessible by both your XML clients and JSON clients (like regular Delphi or Ajax clients).

Offline

#7 2014-08-04 16:24:46

cheemeng
Member
From: Malaysia
Registered: 2011-08-09
Posts: 61

Re: Switching Response Format (XML/JSON)

Arnaud could you add text/xml too?

From the RFC (3023), under section 3, XML Media Types:

If an XML document -- that is, the unprocessed, source XML document -- is readable by casual users, text/xml is preferable to application/xml. MIME user agents (and web user agents) that do not have explicit support for text/xml will treat it as text/plain, for example, by displaying the XML MIME entity as plain text. Application/xml is preferable when the XML MIME entity is unreadable by casual users.

Thank you once again!

Offline

#8 2014-08-04 19:49:11

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

Re: Switching Response Format (XML/JSON)

Now 'Accept: text/xml' will be recognized in addition to previous 'Accept: application/xml'.
See http://synopse.info/fossil/info/40b2c25 … 1798d76280

I've also updated the documentation, and will soon post a blog article about this feature.
Thanks for the feedback!

Offline

#9 2014-08-06 08:24:12

mpv
Member
From: Ukraine
Registered: 2012-03-24
Posts: 1,534
Website

Re: Switching Response Format (XML/JSON)

ab, to continue this good innovation I want to share my code able to serialize TSQLTableJSON to schemas-microsoft-com:rowset XML format (format used by MS Recordset).
We use this type of serialization to pass server result directly to MS application. Actually we build very powerfull Excell based client using this feature.
I send server-side code to You (can be easy integrated to mORMot).
Client side code part VBA(Excell, Word, e.t.c.) example (my authorization (GetAuthToken) function is not as in mORMot, so may be someone write pure mORMot example):

' in case of success return "WinHttp.WinHttpRequest.5.1" instanse
' in case of error - Nothing and put error message to ErrorMessage property
' Warning: do not forgot to set result to Nothing after usage
Private Function CustomRequest(aURL As String, aURLParams As String, aHTTPMethod As String, aBody)
  Dim lngTimeout
  Dim strUserAgentString
  Dim intSslErrorIgnoreFlags
  Dim blnEnableRedirects
  Dim blnEnableHttpsToHttpRedirects
  Dim objWinHttp
  Dim strURL As String
  
  Set CustomRequest = Nothing
  fErrorMessage = ""
  lngTimeout = 59000
  strUserAgentString = "http_requester/0.1"
  intSslErrorIgnoreFlags = 13056 ' Ignore Errors
  blnEnableRedirects = True
  blnEnableHttpsToHttpRedirects = True
  Set objWinHttp = CreateObject("WinHttp.WinHttpRequest.5.1")
  objWinHttp.SetTimeouts lngTimeout, lngTimeout, lngTimeout, lngTimeout
  
  strURL = Server & "/" & aURL & "?" & aURLParams & GetAuthToken
  
  On Error Resume Next
  objWinHttp.Open aHTTPMethod, strURL
  If Err.Number <> 0 Then
    fErrorMessage = "Error " & Err.Number & " " & Err.Source & " " & Err.Description
    Exit Function
  End If
  If aHTTPMethod = "POST" Then
    objWinHttp.SetRequestHeader "Content-type", "text/xml; charset=UTF-8"
  End If

  objWinHttp.Option(0) = strUserAgentString
  objWinHttp.Option(4) = intSslErrorIgnoreFlags
  objWinHttp.Option(6) = blnEnableRedirects
  objWinHttp.Option(12) = blnEnableHttpsToHttpRedirects

  objWinHttp.Send (aBody)
  If Err.Number = 0 Then
    If objWinHttp.Status = 200 Then
      Set CustomRequest = objWinHttp
    ElseIf objWinHttp.Status = 401 Then
      fSessionID = Empty ' in case of unauthorized request client must relogon
    Else
      fErrorMessage = "HTTP " & objWinHttp.Status & " " & objWinHttp.StatusText & " " & objWinHttp.ResponseText
    End If
  Else
    fErrorMessage = "HTTP " & objWinHttp.Status & " " & objWinHttp.StatusText & " " & objWinHttp.ResponseText
    If fErrorMessage = "" Then fErrorMessage = Err.Description
  End If
  On Error GoTo 0
  Set objWinHttp = Nothing
End Function

Private Function RecordsetFromStream(ByRef aStream) As recordset
    Set RecordsetFromStream = New ADODB.recordset
    RecordsetFromStream.Open aStream    'Open a recordset from the stream
End Function

Set req = CustomRequest("runList", "", "POST", postData)
If req Is Nothing Then
   Set rs = Nothing
Else
   Set rs = RecordsetFromStream(req.ResponseStream)
End If

Offline

#10 2014-08-06 11:04:36

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

Re: Switching Response Format (XML/JSON)

We have added TSQLTable.GetMSRowSetValues() methods, to return XML content in ADODB.recordset format.
I did not test the output, but sounds matching what you sent to me.
See http://synopse.info/fossil/info/99f74a1c78d

Thanks mpv and Vadim Orel for the input!
smile

Offline

#11 2014-09-05 07:42:59

cheemeng
Member
From: Malaysia
Registered: 2011-08-09
Posts: 61

Re: Switching Response Format (XML/JSON)

Hi Arnaud,

I would like to propose that the method JSONBufferToXML (SynCommons.pas, line 34345) accepts an optional ArrayName parameter that can be passed to AddJSONToXML. At the same time, perhaps the ArrayName can default to the Model's name, and may be set by a property of TSQLModel?

Thank you!

Offline

#12 2014-09-05 15:55:14

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

Re: Switching Response Format (XML/JSON)

Could you create a feature request ticket?

Offline

#13 2014-09-05 16:23:51

cheemeng
Member
From: Malaysia
Registered: 2011-08-09
Posts: 61

Re: Switching Response Format (XML/JSON)

Offline

#14 2014-10-02 07:15:19

mpv
Member
From: Ukraine
Registered: 2012-03-24
Posts: 1,534
Website

Re: Switching Response Format (XML/JSON)

Just for information - we moved to last mORMot and test TSQLTable.GetMSRowSetValues(). Everything works well, however, as always in AB code

Offline

#15 2014-10-09 12:21:30

esmondb
Member
From: London
Registered: 2010-07-20
Posts: 299

Re: Switching Response Format (XML/JSON)

Could there be another switch, set by the HTTP header, to select between the expanded and non-expanded mORMot JSON formats?

I've been experimenting with javascript clients and it's not that difficult to expand the JSON response client side and this would give the benefit of the terser format for transmission.

Offline

#16 2014-10-10 05:47:14

mpv
Member
From: Ukraine
Registered: 2012-03-24
Posts: 1,534
Website

Re: Switching Response Format (XML/JSON)

I'ts not a problem to expand on client side:

function syn2JS(synJSON){
    var fc = synJSON.fieldCount, values = synJSON.values,
        len = values.length, fNum,
        index = fc, item, result = [];
    while (index < len){
        item = {}; fNum = -1;
        while(++fNum < fc){
            item[values[fNum]] = values[index];
            index++;
        }
        result.push(item);     
    }  
    return result;  
}

var synJSON = JSON.parse('{ "fieldCount":2,"values":["col1","col2","val11","val12","val21","val22"] }');
syn2JS(synJSON);

Last edited by mpv (2014-10-10 05:48:07)

Offline

#17 2014-10-10 06:22:46

esmondb
Member
From: London
Registered: 2010-07-20
Posts: 299

Re: Switching Response Format (XML/JSON)

It would just be convenient to be able to select between expanded/unexpanded in the client request.

But both formats are fine. Here's a jquery plugin which detects the format and feeds it into an html table:

(function ( $ ) {

  $.fn.jsonToTable = function( JSONobj ) {
    var i, ii, rowCount, fieldCount, tableRow, fields,
    table = this;
    if (table.find("thead").length == 0) {
      table.append($("<thead></thead>").append("<tr></tr>"));
    }
    if (table.find("thead").find("tr").length == 0) {
      table.find("thead").append("<tr></tr>");
    }
    table.find("thead").find("tr").empty();
    if (table.find("tbody").length == 0) {
      table.append($("<tbody></tbody>"));
    }
    table.find("tbody").empty();
    if(JSONobj.hasOwnProperty("fieldCount")){
      fieldCount = JSONobj.fieldCount;
      for (i = 0; i < fieldCount; i++) {
        table.find("thead").find("tr").append("<th>"+JSONobj.values[i]+"</th>");
      };
      rowCount = JSONobj.rowCount;
      for (i = 0; i < rowCount; i++) {
        tableRow = $("<tr></tr>");//.addClass(rowClass);
        for ( ii = 0; ii < fieldCount; ii++) {
          tableRow.append($("<td>" + JSONobj.values[ii+((i+1)*fieldCount)] + "</td>"));
        };
        table.append(tableRow);
      };
    } else if (JSONobj.values.length > 0) {
      fields = Object.keys(JSONobj.values[0]);
      fieldCount = fields.length;
      rowCount = JSONobj.values.length;
      for (i = 0; i < fieldCount; i++) {
        table.find("thead").find("tr").append("<th>"+fields[i]+"</th>");
      };
      for (i = 0; i < rowCount; i++) {
        tableRow = $("<tr></tr>");//.addClass(rowClass);
        for ( ii = 0; ii < fieldCount; ii++) {
          tableRow.append($("<td>" + JSONobj.values[i][fields[ii]] + "</td>"));
        };
        table.append(tableRow);
      };
    }
    return table;
  };
  
}( jQuery ));

Offline

#18 2015-01-28 07:03:11

berndvf
Member
Registered: 2013-03-08
Posts: 16

Re: Switching Response Format (XML/JSON)

ab wrote:

Yes, I'm currently adding XMLToJSON and JSONToXML functions to SynCommons.pas.

Hi Arnaud,

Was this ever implemented?

Thanks,
Bernd

Offline

#19 2015-01-28 09:34:03

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

Re: Switching Response Format (XML/JSON)

You have JSONToXML() JSONBufferToXML() and TTextWriter.JSONBufferToXML() methods available.

But the other direction, i.e. XMLToJSON(), can not be implemented easily, since XML does not match JSON, without a conversion scheme (e.g. how to convert attributes? how to store types?).
You can not easily convert SOAP XML into raw JSON, since the values may be nested in other nodes.

Offline

Board footer

Powered by FluxBB