Question
· Feb 28, 2019

Stream Binary to JSON work around. HealthShare 2017.2

Hi Dev Community,

My team is currently working on a project where we need to post pdf documents to a 3rd party REST API.

The API specifies a field in the JSON request message to contain the 'physical content' of the file as an array of bytes, example of the JSON request as follows - data truncated for readability purposes:

{
    "CaptureSource":2,
    "RecipientID":"ID34",
    "Document": {
        "Guid":"5D847A4E9CC1485382DC4A5F5DC80D6C",
        "EventDate":"2019-01-14T13:23:46",
        "FileExtension":"pdf",
        "FileContent":"JVBERi0xLjQNCiXi48/TDQoxIDAgb2JqDQogIDw8DQogICA......"

      },
    "ActionRequired":false,

}

COB projects array of bytes as %Stream.GlobalBinary stream type. We are able to see the physical contents of the files within our production when we read the files and they travel through the business hosts in our production, but when we call the utility method: 

Set tSC=##class(Ens.Util.JSON).ObjectToJSONStream(pRequest,.tJsonPayload,"iuw")

 

The output blanks out the FileContent element of the JSON message - pRequest being a custom request object with a Property FileContent As %Stream.GlobalBinary , and crashes the API call:

{
    "CaptureSource":2,
    "RecipientID":"ID34",
    "Document": {
        "Guid":"5D847A4E9CC1485382DC4A5F5DC80D6C",
        "EventDate":"2019-01-14T13:23:46",
        "FileExtension":"pdf",
        "FileContent":null
    },

 I've tried creating a %DynamicObject instead and calling %To.JSON(), but apparently Binary Streams are not supported:

W !, tDynamicObj.%ToJSON()     

^
<METHOD NOT SUPPORTED>zTestByteArray+9^UCLH.SIP.Test.Utilities.1 *%ToJSON,%Stream.FileBinary

We have contacted the vendor of the API to double check if they are able to process an E64 encoded version of the file content, but they are expecting it as a byte array so our choices here are zero to none. If we use POSTMAN and copy across the contents of the file into a JSON request the call works like a charm.

Is anybody aware of any workaround available? I have tried to output the %Stream.GlobalBinary as a String but it logically outputs the content of the pdf file a a stream of characters:

"FileContent":"%PDF-1.4\r\n%âãÃÃ\r\n1 0 obj\r\n <<\r\n \/Author()\/Title()\/Subject()\/Producer()\/Keywords()\/CreationDate(D:20190114132345+00'00')\/ModDate(D:20190114132345+00'00')\/Creator(CSSystems Corporation)\r\n >>\r\nendobj\r\n4 0 obj\r\n <<\/Filter \/FlateDecode \/Length 1314>>\r\n stream\r\nxÂíÂmoÃ6¿øomÂ4!%R\"Âa@....

Discussion (3)2
Log in or sign up to continue

I think the solution is to use character stream and made manual Base64Encoding/Decoding.

I have created two methods for it:

/// Flags - 0 - Insert CR/LF after every 76 characters (Default)<br>
/// Flags - 1 - Do not insert CR/LF after every 76 characters.<br>
ClassMethod Base64Encode(tIn As %Stream.TmpBinary, Output tOut As %Stream.TmpCharacter, chunk As %Integer = 32000, Flags As %Integer = 1) As %Status
{
   set sc = $$$OK
   if $g(tIn)="" quit $$$ERROR(5001, "Input stream required")
   if '$IsObject(tIn) quit $$$ERROR(5001,"Input is not a stream object")
   if 'tIn.%IsA("%Stream.Object") quit $$$ERROR(5001,"Input object is not a stream")
   If '$IsObject($g(tOut)) {
    set tOut=##class(%Stream.TmpCharacter).%New()
   }
   if 'tOut.%IsA("%Stream.Object") quit $$$ERROR(5001,"Output object is not a stream")
   set chunk=chunk-(chunk#3)
   do tIn.Rewind()
   While 'tIn.AtEnd {
    set sc= tOut.Write($SYSTEM.Encryption.Base64Encode(tIn.Read(chunk),Flags))
    if 'sc Quit
   }
   Quit sc
}

ClassMethod Base64Decode(tIn As %Stream.TmpBinary, Output tOut As %Stream.TmpCharacter, chunk As %Integer = 32000) As %Status
{
   set sc = $$$OK
   if $g(tIn)="" quit $$$ERROR(5001,"Input stream required")
   if '$IsObject(tIn) quit $$$ERROR(5001,"Input is not a stream object")
   if 'tIn.%IsA("%Stream.Object") quit $$$ERROR(5001,"Input object is not a stream")
   If '$IsObject($g(tOut)) {
    set tOut=##class(%Stream.TmpCharacter).%New()
   }
   if 'tOut.%IsA("%Stream.Object") quit $$$ERROR(5001,"Output object is not a stream")
   set chunk=chunk-(chunk#4)
   do tIn.Rewind()
   While 'tIn.AtEnd {
    set sc= tOut.Write($SYSTEM.Encryption.Base64Decode(tIn.Read(chunk)))
    if 'sc Quit
   }
   Quit sc
}
 

Then in the code you need to call it e.g.:

  set sc = ..Base64Encode(tPDF,tDocRequest.FileContent,,1)
               

where 'tPDF' is a stream object with original PDF content, tDocRequest.FileContent then will contain given PDF encoded to Base64 - this is what binary streams do automatically.