Format the value from Long Text Area field that holds a json string into a more readable format that looks almost like a real json.
The full blog post: https://salesforcepanda.com/capturing-api-request/
To the youtube video: https://youtu.be/48aGuvLjONU


- Custom object: API_Request__c
- Custom fields:
- ChangedFields__c – Long Text Area(32768)
- Method__c – Text(255)
- RequestBody__c – Long Text Area(32768)
- sObject__c – Text(255)
- userId__c – Text(255)
- Custom fields:
Apex Classes to retrieve the fields to format later*
Apex Class: jsonViewerRequestBody
public with sharing class jsonViewerRequestBody {
@AuraEnabled(cacheable=true)
public static String getRequestBody(Id recordId) {
API_Request__c req = [SELECT requestBody__c FROM API_Request__c WHERE Id = :recordId LIMIT 1];
return req.requestBody__c;
}
}
Code language: JavaScript (javascript)
Apex Class: jsonViewerChangedFields
public with sharing class jsonViewerChangedFields {
@AuraEnabled(cacheable=true)
public static String getRequestBody(Id recordId) {
API_Request__c req = [SELECT ChangedFields__c FROM API_Request__c WHERE Id = :recordId LIMIT 1];
return req.ChangedFields__c;
}
}
Code language: JavaScript (javascript)
*if the field you need to display is actually on the very record where you display the LWC the apex class is not required. To see this check down below: LWC without apex class
jsonViewerLWC
HTML
<template>
<lightning-card title="API Request Body">
<template if:true={isLoading}>
<lightning-spinner alternative-text="Loading..." size="medium"></lightning-spinner>
</template>
<template if:true={errorMessage}>
<p class="error-message">{errorMessage}</p>
</template>
<template if:true={formattedJson}>
<div class="json-container" lwc:dom="manual"></div>
<!-- SLDS Icon Button for Copy -->
<lightning-button-icon
icon-name="utility:copy"
alternative-text="Copy to Clipboard"
class="copy-button"
onclick={copyToClipboard}>
</lightning-button-icon>
</template>
</lightning-card>
</template>
Code language: HTML, XML (xml)
java script
import { LightningElement, api, wire, track } from 'lwc';
import getRequestBody from '@salesforce/apex/jsonViewerRequestBody.getRequestBody';
export default class JsonViewer extends LightningElement {
@api recordId;
@track formattedJson;
@track isLoading = true;
@track errorMessage;
@wire(getRequestBody, { recordId: '$recordId' })
wiredRequestBody({ error, data }) {
this.isLoading = false;
if (data) {
try {
this.formattedJson = JSON.stringify(JSON.parse(data), null, 2);
this.errorMessage = null;
requestAnimationFrame(() => this.applySyntaxHighlighting());
} catch (e) {
this.formattedJson = null;
this.errorMessage = 'Invalid JSON format';
}
} else if (error) {
this.formattedJson = null;
this.errorMessage = 'Error loading JSON: ' + JSON.stringify(error);
}
}
applySyntaxHighlighting() {
const jsonContainer = this.template.querySelector('.json-container');
if (jsonContainer) {
jsonContainer.innerHTML = this.syntaxHighlight(this.formattedJson);
}
}
syntaxHighlight(json) {
if (!json) return '';
// Regex to match keys, string values, numbers, booleans, null values, and backslashes separately
return json.replace(/\\|("(\\.|[^"\\])*"(?=\s*:))|("(\\.|[^"\\])*")|(\b\d+\b|\btrue\b|\bfalse\b|null)/g, (match, keyMatch, key, valueMatch, value, otherMatch) => {
let cls = 'json-string'; // Default class for values
// If it's a backslash, apply "json-backslash" class (white)
if (match === '\\') {
return `<span class="json-backslash">${match}</span>`;
}
// If it's a key (before the colon), apply "json-key" class (light blue)
if (keyMatch) {
return `<span class="json-key">${keyMatch}</span>`;
}
// If it's a string value (inside double quotes), apply "json-string" class (green)
else if (valueMatch) {
// Apply green color to the whole string value
return `<span class="json-string">${valueMatch.replace(/\\/g, '<span class="json-backslash">\\</span>')}</span>`;
}
// Handle numbers, booleans, and null values
else if (otherMatch) {
if (/\b\d+\b/.test(otherMatch)) {
cls = 'json-number'; // Purple for numbers
} else if (/\btrue\b|\bfalse\b/.test(otherMatch)) {
cls = 'json-boolean'; // Orange for booleans
} else if (/null/.test(otherMatch)) {
cls = 'json-null'; // Red for null
}
return `<span class="${cls}">${otherMatch}</span>`;
}
return match;
});
}
copyToClipboard() {
const jsonText = this.formattedJson;
if (jsonText) {
// Create a temporary text area to hold the JSON text
const textArea = document.createElement('textarea');
textArea.value = jsonText;
document.body.appendChild(textArea);
textArea.select();
try {
// Execute the command to copy the content
const successful = document.execCommand('copy');
if (successful) {
alert('JSON copied to clipboard!');
} else {
alert('Unable to copy to clipboard.');
}
} catch (err) {
alert('Error copying to clipboard: ' + err);
}
// Clean up the temporary text area
document.body.removeChild(textArea);
}
}
// Expand/collapse nested JSON objects
toggleCollapse(event) {
const target = event.target;
const parentNode = target.closest('.json-item');
const childNodes = parentNode.querySelectorAll('.nested-json');
if (childNodes.length > 0) {
childNodes.forEach(node => {
node.style.display = node.style.display === 'none' ? 'block' : 'none';
});
target.textContent = target.textContent === 'Expand' ? 'Collapse' : 'Expand';
}
}
}
Code language: JavaScript (javascript)
CSS
/* Apply a black background to the entire component */
:host {
--sds-c-card-color-background: #494747;
color: #f8f8f2;
}
/* Ensure the card has a black background */
.slds-card {
position: relative; /* Needed for absolute positioning of the button */
background-color: #494747; /* Dark background for the card */
border-radius: 5px; /* Optional: add rounded corners to the card */
box-shadow: none; /* Optional: remove shadow for a cleaner look */
padding-top: 20px; /* Adds padding to the top */
overflow: hidden; /* Ensure the card content is contained within the card */
}
/* Customize the title to match the card background */
.slds-card__header {
background-color: #494747; /* Same dark background as the card */
color: #f8f8f2; /* Light off-white color for the title */
border-radius: 5px 5px 0 0; /* Round the top corners */
}
/* JSON container */
.json-container {
font-family: monospace;
white-space: pre-wrap;
word-wrap: break-word;
background: #494747;
color: #f8f8f2;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
}
/* JSON key colors */
.json-key { color: #66d9ef; } /* Light Blue for keys */
.json-string { color: #a6e22e; } /* Green for string values */
.json-number { color: #ae81ff; } /* Purple for numbers */
.json-boolean { color: #fd971f; } /* Orange for booleans */
.json-null { color: #f92672; } /* Red for null */
.json-backslash { color: #ffffff; } /* White for backslashes */
/* Error message styling */
.error-message {
color: red;
font-weight: bold;
}
/* Copy button positioning */
.copy-button {
position: absolute;
top: 10px;
right: 10px;
border: none;
padding: 0;
z-index: 10;
cursor: pointer;
}
/* Set the icon color to white */
.copy-button lightning-button-icon {
color: white !important; /* Force the icon to be white */
}
/* Button hover effect */
.copy-button:hover {
opacity: 0.7;
}
Code language: CSS (css)
xml
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="JsonViewer">
<apiVersion>58.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<property name="recordId" type="String" label="Record ID" required="true"/>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Code language: HTML, XML (xml)
LWC without apex class
IF you display the LWC on the record that also holds the field you want to format you don’t need an apex class.
For your use change
import REQUEST_FIELDS from '@salesforce/schema/API_Request__c.RequestBody__c';
Code language: JavaScript (javascript)
and
this.formattedJson = JSON.stringify(JSON.parse(data.fields.RequestBody__c.value), null, 2);
Code language: JavaScript (javascript)
Java Script
import { LightningElement, api, wire, track } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
// Import the field you need
import REQUEST_FIELDS from '@salesforce/schema/API_Request__c.RequestBody__c';
export default class JsonViewer extends LightningElement {
@api recordId;
@track formattedJson;
@track isLoading = true;
@track errorMessage;
// Use getRecord instead of Apex
@wire(getRecord, { recordId: '$recordId', fields: [REQUEST_FIELDS] })
wiredRecord({ error, data }) {
this.isLoading = false;
if (data) {
try {
this.formattedJson = JSON.stringify(JSON.parse(data.fields.RequestBody__c.value), null, 2);
this.errorMessage = null;
requestAnimationFrame(() => this.applySyntaxHighlighting());
} catch (e) {
this.formattedJson = null;
this.errorMessage = 'Invalid JSON format';
}
} else if (error) {
this.formattedJson = null;
this.errorMessage = 'Error loading JSON: ' + JSON.stringify(error);
}
}
applySyntaxHighlighting() {
const jsonContainer = this.template.querySelector('.json-container');
if (jsonContainer) {
jsonContainer.innerHTML = this.syntaxHighlight(this.formattedJson);
}
}
syntaxHighlight(json) {
if (!json) return '';
// Regex to match keys, string values, numbers, booleans, null values, and backslashes separately
return json.replace(/\\|("(\\.|[^"\\])*"(?=\s*:))|("(\\.|[^"\\])*")|(\b\d+\b|\btrue\b|\bfalse\b|null)/g, (match, keyMatch, key, valueMatch, value, otherMatch) => {
let cls = 'json-string'; // Default class for values
// If it's a backslash, apply "json-backslash" class (white)
if (match === '\\') {
return `<span class="json-backslash">${match}</span>`;
}
// If it's a key (before the colon), apply "json-key" class (light blue)
if (keyMatch) {
return `<span class="json-key">${keyMatch}</span>`;
}
// If it's a string value (inside double quotes), apply "json-string" class (green)
else if (valueMatch) {
// Apply green color to the whole string value
return `<span class="json-string">${valueMatch.replace(/\\/g, '<span class="json-backslash">\\</span>')}</span>`;
}
// Handle numbers, booleans, and null values
else if (otherMatch) {
if (/\b\d+\b/.test(otherMatch)) {
cls = 'json-number'; // Purple for numbers
} else if (/\btrue\b|\bfalse\b/.test(otherMatch)) {
cls = 'json-boolean'; // Orange for booleans
} else if (/null/.test(otherMatch)) {
cls = 'json-null'; // Red for null
}
return `<span class="${cls}">${otherMatch}</span>`;
}
return match;
});
}
copyToClipboard() {
const jsonText = this.formattedJson;
if (jsonText) {
// Create a temporary text area to hold the JSON text
const textArea = document.createElement('textarea');
textArea.value = jsonText;
document.body.appendChild(textArea);
textArea.select();
try {
// Execute the command to copy the content
const successful = document.execCommand('copy');
if (successful) {
alert('JSON copied to clipboard!');
} else {
alert('Unable to copy to clipboard.');
}
} catch (err) {
alert('Error copying to clipboard: ' + err);
}
// Clean up the temporary text area
document.body.removeChild(textArea);
}
}
// Expand/collapse nested JSON objects
toggleCollapse(event) {
const target = event.target;
const parentNode = target.closest('.json-item');
const childNodes = parentNode.querySelectorAll('.nested-json');
if (childNodes.length > 0) {
childNodes.forEach(node => {
node.style.display = node.style.display === 'none' ? 'block' : 'none';
});
target.textContent = target.textContent === 'Expand' ? 'Collapse' : 'Expand';
}
}
}
Code language: JavaScript (javascript)
Leave a Reply