MS365BC JSON Viewer FactBox

In my daily work I often encounter interfaces and web services of any kind. Whether inbound or outbound, they usually use some kind of payload in the form of JSON these days. No matter if it’s the outgoing object or the response of the web service.

Since I fundamentally implement strong logging in my extensions, it would be great if you could see the content of these requests and responses in e.g. the logging overview. This involves me storing the JSON-data in blob fields in my logging table.

To achieve this visualization, I created a little and universal MS365BC JSON Viewer FactBox. You could embed this FactBox where ever you have some kind of JSON InStream to show. It uses a free JavaScript component.

image 2

When importing both request as well as the response JSON, you could switch between them using an action.

MS365BC JSON Viewer FactBox

Preparing the AL project

AL

To start we have to create our control addin. Here we define the resources to load and the basic sizing of our FactBox.

controladdin JsonViewer
{
    StartupScript = 'src/Ressources/Scripts/json-viewer-startup.js';
    Scripts = 'src/Ressources/Scripts/json-viewer.js', 'src/Ressources/Scripts/json-viewer-functions.js';
    StyleSheets = 'src/Ressources/StyleSheets/json-viewer.css';

    HorizontalStretch = true;
    HorizontalShrink = true;
    MinimumWidth = 250;

    VerticalShrink = true;
    VerticalStretch = true;
    RequestedHeight = 550;

    event OnControlAddInReady();
    event OnJsonViewerReady();

    procedure InitializeControl();
    procedure LoadDocument(data: JsonObject; maxLvl: Integer; colAt: Integer);
}

In the next step we create the MSBC365 JSON Viewer FactBox itself.

/// <summary>
/// JSONViewer FactBox
/// </summary>
page 50013 "API Log Json Viewer"
{
    PageType = CardPart;
    Caption = 'JSON Viewer';
    UsageCategory = None;

    layout
    {
        area(Content)
        {
            field("Query Direction"; QueryDirection)
            {
                Visible = ShowQueryDirection;
                OptionCaption = 'Request,Response';
                Caption = 'Query Direction';
                ToolTip = 'Choose Query Direction';
            }
            usercontrol(JsonViewer; JsonViewer)
            {
                ApplicationArea = All;
                trigger OnControlAddInReady()
                begin
                    InitializeJsonViewer();
                end;

                trigger OnJsonViewerReady()
                begin
                    IsInitialized := true;
                    ShowData();
                end;
            }

        }
    }
    actions
    {
        area(Processing)
        {
            action(Request)
            {
                Visible = ShowQueryDirection;
                ApplicationArea = All;
                Image = BreakRulesOn;
                Enabled = QueryDirection = QueryDirection::Response;

                Caption = 'Request';
                ToolTip = 'Request';
                trigger OnAction()
                begin
                    QueryDirection := QueryDirection::Request;
                    ShowData();
                end;
            }
            action(Response)
            {
                Visible = ShowQueryDirection;
                ApplicationArea = All;
                Image = BreakRulesOff;
                Enabled = QueryDirection = QueryDirection::Request;

                Caption = 'Response';
                ToolTip = 'Response';
                trigger OnAction()
                begin
                    QueryDirection := QueryDirection::Response;
                    ShowData();
                end;
            }
        }
    }

    var
        ShowQueryDirection: Boolean;
        QueryDirection: Option Request,Response;
        IsInitialized: Boolean;
        JObjectRequest: JsonObject;
        JObjectResponse: JsonObject;

    local procedure InitializeJsonViewer()
    begin
        CurrPage.JsonViewer.InitializeControl();
    end;

    local procedure ShowData()
    begin
        if not IsInitialized then
            exit;

        //json: json Input value
        //maxLvl: Process only to max level, where 0..n, -1 unlimited
        //colAt: Collapse at level, where 0..n, -1 unlimited

        if QueryDirection = QueryDirection::Request then
            CurrPage.JsonViewer.LoadDocument(JObjectRequest, -1, 1)
        else
            CurrPage.JsonViewer.LoadDocument(JObjectResponse, -1, 1);
    end;

    procedure SetContent(lIStreamRequest: InStream; lIStreamResponse: InStream)
    begin
        if not JObjectRequest.ReadFrom(lIStreamRequest) then
            Clear(JObjectRequest);

        if not JObjectResponse.ReadFrom(lIStreamResponse) then
            Clear(JObjectResponse);

        ShowQueryDirection := true;
        ShowData();
    end;

    procedure SetRequestContent(lIStream: InStream)
    begin
        if not JObjectRequest.ReadFrom(lIStream) then
            Clear(JObjectRequest);

        ShowData();
    end;

    procedure SetResponseContent(lIStream: InStream)
    begin
        if not JObjectResponse.ReadFrom(lIStream) then
            Clear(JObjectResponse);

        ShowData();
    end;
}

JavaScript


function InitializeControl() {
    var controlAddIn = document.getElementById('controlAddIn');
    controlAddIn.innerHTML = '<div style="height:100%;overflow-y:auto;" id="json"></div>';

    Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('OnJsonViewerReady', null);
}

function LoadDocument(data, maxLvl, colAt) {
    var jsonViewer = new JSONViewer();

    document.querySelector("#json").innerHTML = '';
    document.querySelector("#json").appendChild(jsonViewer.getContainer());

    jsonViewer.showJSON(data, maxLvl, colAt);
}
//https://www.kauffmann.nl/2019/02/01/controlling-the-size-of-a-control-add-in/
var iframe = window.frameElement;

iframe.parentElement.style.display = 'flex';
iframe.parentElement.style.flexDirection = 'column';
iframe.parentElement.style.flexGrow = '1';

iframe.style.removeProperty('height');
iframe.style.removeProperty('max-height');

iframe.style.flexGrow = '1';
iframe.style.flexShrink = '1';
iframe.style.flexBasis = 'auto';
iframe.style.paddingBottom = '42px';
//iframe.style.height = '1000px';
//iframe.style.overflow = 'hidden';

Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('OnControlAddInReady', null);

The JSON-Viewer component could be downloaded for free over here. Thanks to Roman Makudera for providing such a pretty little helper.

/**
 * JSONViewer - by Roman Makudera 2016 (c) MIT licence.
 */

//https://www.cssscript.com/minimal-json-data-formatter-jsonviewer/
//...

Stylesheet

To make it look pretty, we setup our css definition via stylesheet.

.json-viewer {
	color: #000;
	padding-left: 20px;
}

.json-viewer ul {
	list-style-type: none;
	margin: 0;
	margin: 0 0 0 1px;
	border-left: 1px dotted #ccc;
	padding-left: 2em;
}

.json-viewer .hide {
	display: none;
}

.json-viewer .type-string {
	color: #0B7500;
}

.json-viewer .type-date {
	color: #CB7500;
}

.json-viewer .type-boolean {
	color: #1A01CC;
	font-weight: bold;
}

.json-viewer .type-number {
	color: #1A01CC;
}

.json-viewer .type-null, .json-viewer .type-undefined {
	color: #90a;
}

.json-viewer a.list-link {
	color: #000;
	text-decoration: none;
	position: relative;
}

.json-viewer a.list-link:before {
	color: #aaa;
	content: "\25BC";
	position: absolute;
	display: inline-block;
	width: 1em;
	left: -1em;
}

.json-viewer a.list-link.collapsed:before {
	content: "\25B6";
}

.json-viewer a.list-link.empty:before {
	content: "";
}

.json-viewer .items-ph {
	color: #aaa;
	padding: 0 1em;
}

.json-viewer .items-ph:hover {
	text-decoration: underline;
}

Usage of the MS365BC JSON Viewer FactBox

After creating these AL files and embedding them in your project you could add the FactBox to whatever page you like. You could then fill it with whatever InStream containing your JSON content in the OnAfterGetCurrRecord() trigger.

First we define the FactBox in our desired Page:

area(FactBoxes)
{
    part(JsonViewer; "API Log Json Viewer")
    {
        ApplicationArea = All;
    }
}

And now add the data in OnAfterGetCurrRecord() trigger:

trigger OnAfterGetCurrRecord()
var
    lIStreamRequest: InStream;
    lIStreamResponse: InStream;
begin
    Rec.CalcFields("Request Content", "Response Content");
    Rec."Request Content".CreateInStream(lIStreamRequest);
    Rec."Response Content".CreateInStream(lIStreamResponse);
    CurrPage.JsonViewer.Page.SetContent(lIStreamRequest, lIStreamResponse);
end;

You can find the files on GitHub as well.

... is a technical consultant and developer at Comsol Unternehmenslösungen AG in Kronberg/Taunus. Major tasks are the architecture and implementation of complex, usually cross-system applications in and around Microsoft Dynamics 365 Business Central.

Leave a Reply

Your email address will not be published. Required fields are marked *