- 7 minutes to read

Base64-decode XML from JSON with a Liquid Stylesheet

Decode base64-encoded XML embedded in JSON messages and render it as readable XML in Nodinite Log Views. This Liquid-based Stylesheet enables operators to preview, inspect, and download XML content directly—no additional tooling required.

Benefits:

  • ✅ View embedded XML inline without external tools
  • ✅ Decode base64 client-side for fast previews and diagnostics
  • ✅ Export rendered XML for sharing or offline analysis

Base64-decode the order XML from a Service Bus JSON message with a Liquid Stylesheet in a Log View, animated walkthrough (7.12)
Animated walkthrough: View message body shows the JSON with the base64 text in body.ContentData; View message formatted as the Stylesheet shows the decoded order XML, å included, in a code editor.

graph LR Json["fa:fa-file-code JSON Message
with base64-encoded XML"] --> stylesheet["fa:fa-droplet Liquid Stylesheet
with JavaScript"] stylesheet -->|"Transform"| xml["fa:fa-file-lines Readable XML
in the Log View"] style Json fill:#2678BD,color:#fff style stylesheet fill:#E47528,color:#fff style xml fill:#93d16d,color:#000

The JSON input (blue) passes the Liquid Stylesheet (orange) and appears as readable XML (green). The StylesheetScriptsEnabled System Parameter must be true for the JavaScript to run.

This example demonstrates how to transform a JSON message containing base64-encoded XML into rendered XML output using a Liquid Stylesheet. Use this approach to make data accessible to end-users while optionally removing sensitive information.

1. Input JSON

The following data is an example of a JSON message with a base64 encoded content:

{
	"method": "post",
	"queries": {
		"systemProperties": "Run Details"
	},
	"path": "/orders/messages",
	"host": {
		"connection": {
			"name": "/subscriptions/ae3da538-3e2b-4490-9feb-7dfe30a6683c/resourceGroups/Default-Storage-NorthEurope/providers/Microsoft.Web/connections/servicebus"
		}
	},
	"body": {
		"ContentData": "PG5zMDpPcmRlcnMgeG1sbnM6bnMwPSJTdXBwbHlDaGFpbi5TY2hlbWFzLzEuMCI+CiAgPE9yZGVyPgogICAgPElkPjEzMzc8L0lkPgogICAgPEFtb3VudD4xMzM3PC9BbW91bnQ+CiAgICA8Q2l0eT5Ta2VsbGVmdGXDpTwvQ2l0eT4KICA8L09yZGVyPgo8L25zMDpPcmRlcnM+",
		"CorrelationId": "BOWC123",
		"Properties": {
			"ExtendedProperties/1.0#SystemInterchangeId": "BOWC123"
		}
	}
}

2. Liquid Stylesheet

Use a Liquid Stylesheet to decode and render the base64-encoded XML content.

Warning

Script Execution Requirement: This stylesheet contains embedded JavaScript code. The StylesheetScriptsEnabled system parameter must be set to true for this stylesheet to execute. The default is false on a new installation; an upgraded installation keeps its value. Only members of the built-in Administrators role can enable this parameter. See StylesheetScriptsEnabled for security considerations.

Important

Configuration Required: Enable "Message Body is Json" in the Stylesheet settings. When enabled, Nodinite parses the JSON message and injects properties as Liquid variables, allowing you to access nested properties like {{body['ContentData']}} directly in your Liquid template. Without it, {{body['ContentData']}} is empty and nothing is decoded.

Stylesheet settings with Type Liquid, Render as HTML and Message Body is Json checked, bound to the Message Type Contoso.ServiceBus.OrderEnvelope/1.0 (7.12)
The Stylesheet's settings: Type Liquid, Render as HTML and Message Body is Json checked, and the Message Type of the JSON messages under Message Types.

	<textarea rows="50" cols="50" style="border: none;outline: none;" id="demo"> </textarea>
	<pre id="demoprint" style="margin:0; padding:0; border:none; display:none;"></pre>
	<script>
		var editor;
		function codemirroreditor(elem) {
			var mime = 'application/xml';
			 editor = CodeMirror.fromTextArea(elem, {
				lineNumbers: true,
				mode: mime,
				indentWithTabs: true,
				smartIndent: true,
				lineNumbers: true,
				matchBrackets: true,
				lineWrapping: true,
				autofocus: true,
				autoRefresh: true,
				extraKeys: { "Ctrl-Space": "autocomplete" }
			});
		}

		function b64DecodeUnicode(str) {
			return decodeURIComponent(
				atob(str)
					.split('')
					.map(function(c) {
						return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
					})
					.join('')
				);
		}


		var content = b64DecodeUnicode("{{body['ContentData']}}");
		function displayXml() {
			var elem = document.getElementById('demo');
			elem.value = content;
			codemirroreditor(elem);
		};

		function htmlEntities(str) {
			var htmlString = String(str).replace(/&/g, '&amp;').replace(/</g,'&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
			return htmlString;
		}

		window.addEventListener('beforeprint', (event) => {
			$(editor.getWrapperElement()).hide();
			$("#demoprint").html(htmlEntities(content)).show();
		});

		window.addEventListener('afterprint', (event) => {
			$(editor.getWrapperElement()).show();
			$("#demoprint").hide();
		});

		displayXml();
		</script>

<style>
  html {height:calc(100vh - 200px); min-height:500px;}
  body {height:100%;}
  .CodeMirror {
    min-height: 200px;
    height: 100%;
  }
</style>

3. XML Output

Applying the Liquid Stylesheet to the Input JSON produces the following XML output:

<ns0:Orders xmlns:ns0="SupplyChain.Schemas/1.0">
  <Order>
    <Id>1337</Id>
    <Amount>1337</Amount>
    <City>Skellefteå</City>
  </Order>
</ns0:Orders>

In the Log View, View message formatted as the Stylesheet shows the same XML:

View message body formatted as Contoso - Order XML from Service Bus envelope with the decoded order XML, Skelleftea with the Swedish a-ring included (7.12)
The decoded order XML in the Log View.

Note

This example performs Unicode translation to properly handle special characters like "å" in the City field.

Without Message Body is Json

Unticked, the JSON body reaches the template raw, as {{data}}, and {{body['ContentData']}} renders empty. View message formatted as the Stylesheet then shows an empty editor:

The same Stylesheet settings with Message Body is Json unchecked (7.12)
The Stylesheet with Message Body is Json unchecked.

View message body formatted as Contoso - Order XML from Service Bus envelope with an empty editor (7.12)
Unchecked: nothing to decode, so the editor stays empty.

4. Download Button

Add a download button to allow users to save the rendered XML content locally.

Warning

Script Execution Requirement: This enhanced stylesheet contains embedded JavaScript code. The StylesheetScriptsEnabled system parameter must be set to true for this stylesheet to execute (false is the default on a new installation). See StylesheetScriptsEnabled for security considerations.

Instructions: Adjust the CONTENTTYPE and FILENAME variables in the code below to match your expected file type.

<button class="btn btn-success" id="downloadButton">Download</button>
<style>

  .btn {
      display: inline-block;
      font-weight: 400;
      color: #212529;
      text-align: center;
      vertical-align: middle;
      user-select: none;
      background-color: transparent;
      border: 1px solid transparent;
      padding: 0.375rem 0.75rem;
      font-size: 1rem;
      line-height: 1.5;
      border-radius: 0.25rem;
      transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
      box-sizing: border-box;
      margin: 1rem;
  }    

  .btn-success {
      color: #fff;
      background-color: #68a19b;
      border-color: #68a19b;
      box-shadow: inset 0 1px 0 rgb(255 255 255 / 15%), 0 1px 1px rgb(0 0 0 / 8%);
  }
  html {height:calc(100vh - 200px); min-height:400px;}
  body {height:100%;}
  .CodeMirror {
    min-height: 200px;
    height: calc(100%-40px);
  }
</style>

<textarea rows="50" cols="50" id="demo"></textarea>
<pre id="demoprint" style="margin:0; padding:0; border:none; display:none;"></pre>
<script>
(function() {
  ///// CHANGE THIS
  var CONTENTTYPE = "application/xml";
  var FILENAME = "out.xml";
  ////////////////


    var editor;
    function codemirroreditor(id) {
      var mime = 'application/xml';
      editor = CodeMirror.fromTextArea(
        document.getElementById(id), 
          {
                lineNumbers: true,
                mode: mime,
                indentWithTabs: true,
                smartIndent: true,
                lineNumbers: true,
                matchBrackets: true,
                lineWrapping: true,
                autofocus: true,
                autoRefresh: true,
                extraKeys: { "Ctrl-Space": "autocomplete" }
          }
        );
  }

  function b64DecodeUnicode(str) {
        return decodeURIComponent(atob(str).split('').map(function(c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
  }
    var content = b64DecodeUnicode("{{ body['ContentData'] }}");
  function displayXml() {
    $("#demo").html(content);
    codemirroreditor('demo');
  };

    function htmlEntities(str) {
    var htmlString = String(str).replace(/&/g, '&amp;').replace(/</g,'&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    return htmlString;
  }

    window.addEventListener('beforeprint', (event) => {
      $(editor.getWrapperElement()).hide();
        $("#demoprint").html(htmlEntities(content)).show();
    $("#downloadButton").hide();
    });

    window.addEventListener('afterprint', (event) => {
        $(editor.getWrapperElement()).show();
        $("#demoprint").hide();
    $("#downloadButton").show();
    });
  displayXml();

  function save(filename, data) {
    const blob = new Blob([data], {type: CONTENTTYPE});
    if(window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveBlob(blob, filename);
    }
    else{
      const elem = window.document.createElement('a');
      elem.href = window.URL.createObjectURL(blob);
      elem.download = filename;        
      document.body.appendChild(elem);
      elem.click();        
      document.body.removeChild(elem);
    }
  }

  $("#downloadButton").click(() => { save(FILENAME, content) } ) 

})();
</script>

Escaped XML

If your content is not base64-encoded and contains quotes ("), extract the content using template literals:

var content = `{{body.content}}`;

Escaped XML Example

{
    "body": {
        "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Document Company=\"CONTOSO\\\" ExportType=\"FULL\">\n   <ENTITY ACTIVEDATE=\"\"\n BANKACCOUNTID=\"\"\n VENDORGROUPID=\"\"\n VENDORORGANIZATIONNAME=\"\"/>\n</Document>\n"
    }
}

Next Steps