My first post is about GZip compressed API responses

Do you ever find yourself wondering how to decode GZip API responses in Business Central? Yeah, me neither.

My first post is about GZip compressed API responses
Photo by Jan Antonin Kolar / Unsplash

Hey, welcome to my blog! I've been thinking of opening one for a while, I have a lot of topics I want to talk about, but I could never decide which was supposed to be "the one".

I got stuck thinking that the first post needs to be magical, it needs to impress people, it must depict the author as the best technical writer out there... or something. But then, a colleague came to me for help with his API integration and we couldn't find a straightforward answer on the search engines, so here it goes. This is now that answer (at least I hope so).

My colleague was tasked to integrate BC with some REST thing. Easy, even without LLMs to help (but girl, do LLMs shine when it comes to these integrations). Only problem: the API seemed to only respond with gibberish. Weird characters with absolutely no sense. We got ourselves an encoding issue!

Content-Type: application/json;charset=UTF-8
Content-Encoding: gzip

So how does one deal with compressed responses? Of course, with the Data Compression codeunit in the System Application. This neat little object provides two methods that will help you solve this particular issue: IsGZip and GZipDecompress.

Let's look at the actual methods in the Data Compression Impl. codeunit in case we're having trust issues:

procedure IsGZip(InputInStream: InStream): Boolean
var
    OriginalStream: DotNet Stream;
    ID: array[2] of Byte;
begin
    if (InputInStream.Length < 2) then exit(false);

    OriginalStream := InputInStream;
    InputInStream.Read(ID[1]);
    InputInStream.Read(ID[2]);

    // from GZIP file format specification version 4.3
    // Member header and trailer
    // ID1 (IDentification 1)
    // ID2 (IDentification 2)
    // These have the fixed values ID1 = 31 (0x1f, \037), ID2 = 139 (0x8b, \213), to identify the file as being in gzip format.

    OriginalStream.Position := 0;
    InputInStream := OriginalStream;
    exit((ID[1] = 31) and (ID[2] = 139));
end;

See how IsGZip reads those magic bytes and then resets the position of the stream for us to read later? Quite neat.

procedure GZipDecompress(InputInStream: InStream; DecompressedOutStream: OutStream)
var
    DotNetCompressionMode: DotNet CompressionMode;
begin
    GZipStream := GZipStream.GZipStream(InputInStream, DotNetCompressionMode::Decompress);
    GZipStream.CopyTo(DecompressedOutStream);
    GZipStream.Dispose();
end;

Dear old DotNet comes to the rescue! We can use it because we are accessing the Impl codeunit from its public interface. Trust is restored and we can safely call these methods to calm our headache.

local procedure MyDecompressionProcedure(Response: HttpResponseMessage; var ResponseText: Text)
    var
        DataCompression: Codeunit "Data Compression";
        TempBlob: Codeunit "Temp Blob";
        ResponseInStream, DecompressedInStream : InStream;
        DecompressedOutStream: OutStream;
    begin
        Response.Content.ReadAs(ResponseInStream);
        if DataCompression.IsGZip(ResponseInStream) then begin
            TempBlob.CreateOutStream(DecompressedOutStream);
            DataCompression.GZipDecompress(ResponseInStream, DecompressedOutStream);
            TempBlob.CreateInStream(DecompressedInStream);
            DecompressedInStream.ReadText(ResponseText);
        end;
    end;

So this is it, it may not be much but it's honest work I'm hoping this can help someone (maybe me as soon as I forget about this). See you next time!