Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
RalfHandl
Product and Topic Expert
Product and Topic Expert
Usually REST APIs produce minimized JSON without any line breaks or indentation, which preserves bandwidth and is fine if a machine is reading the produced JSON.

If however the intended audience mainly consists of humans, e.g. if the JSON files need to undergo "code" reviews, it is better to pretty-print the JSON files first and not rely on everyone having a JSON-aware editor at hand.

In my case the JSON was produced by an ABAP server, and Google pointed me to Horst Keller's blog on ABAP and JSON.

My first idea was to call transformation id_indent on my JSON string, but that didn't work. So I set out to write my own JSON-pretty-printing XSL transformation, and that proved to be pretty easy, thanks to the transformation of JSON into JSON-XML built into call transformation:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" version="1.0">
<!--

Pretty-print JSON with ABAP XSLT
See http://help.sap.com/abapdocu_740/en/index.htm?file=abenabap_json_trafos.htm for the internal JSON-XML format

data(lv_in) = `{"Hello":"to the\nwhole \"World\"","I'm":51,"That's":true,"nothing":null,"one":{},"many":[null,false,42,"hi",{"a":"b"},[]]}`.
data lv_out type string.
call transformation zrha_pretty_json source xml lv_in result xml lv_out.

-->

<xsl:output encoding="UTF-8" method="text" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

<xsl:template match="object">
<xsl:param name="indent" select="''" />
<xsl:if test="position() &gt; 1">
<xsl:value-of select="',& #xA;'"/>
</xsl:if>
<xsl:value-of select="$indent" />
<xsl:if test="@name">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>": </xsl:text>
</xsl:if>
<xsl:text>{</xsl:text>
<xsl:if test="*">
<xsl:value-of select="'& #xA;'"/>
<xsl:apply-templates select="*">
<xsl:with-param name="indent" select="concat($indent,' ')" />
</xsl:apply-templates>
<xsl:value-of select="'& #xA;'"/>
<xsl:value-of select="$indent" />
</xsl:if>
<xsl:text>}</xsl:text>
</xsl:template>

<xsl:template match="array">
<xsl:param name="indent" select="''" />
<xsl:if test="position() &gt; 1">
<xsl:value-of select="',& #xA;'"/>
</xsl:if>
<xsl:value-of select="$indent" />
<xsl:if test="@name">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>": </xsl:text>
</xsl:if>
<xsl:text>[</xsl:text>
<xsl:if test="*">
<xsl:value-of select="'& #xA;'"/>
<xsl:apply-templates select="*">
<xsl:with-param name="indent" select="concat($indent,' ')" />
</xsl:apply-templates>
<xsl:value-of select="'& #xA;'"/>
<xsl:value-of select="$indent" />
</xsl:if>
<xsl:text>]</xsl:text>
</xsl:template>

<xsl:template match="str">
<xsl:param name="indent" select="''" />
<xsl:if test="position() &gt; 1">
<xsl:value-of select="',& #xA;'"/>
</xsl:if>
<xsl:value-of select="$indent" />
<xsl:if test="@name">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>": </xsl:text>
</xsl:if>
<xsl:text>"</xsl:text>
<xsl:call-template name="escape">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:template>

<xsl:template match="num|bool">
<xsl:param name="indent" select="''" />
<xsl:if test="position() &gt; 1">
<xsl:value-of select="',& #xA;'"/>
</xsl:if>
<xsl:value-of select="$indent" />
<xsl:if test="@name">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>": </xsl:text>
</xsl:if>
<xsl:value-of select="." />
</xsl:template>

<xsl:template match="null">
<xsl:param name="indent" select="''" />
<xsl:if test="position() &gt; 1">
<xsl:value-of select="',& #xA;'"/>
</xsl:if>
<xsl:value-of select="$indent" />
<xsl:if test="@name">
<xsl:text>"</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>": </xsl:text>
</xsl:if>
<xsl:text>null</xsl:text>
</xsl:template>

<xsl:template name="escape">
<xsl:param name="string"/>
<xsl:choose>
<xsl:when test="contains($string,'&quot;')">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="old" select="'&quot;'"/>
<xsl:with-param name="new" select="'\&quot;'"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string,'\')">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="old" select="'\'"/>
<xsl:with-param name="new" select="'\\'"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string,'& #xA;')">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="old" select="'& #xA;'"/>
<xsl:with-param name="new" select="'\n'"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string,'& #xD;')">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="old" select="'& #xD;'"/>
<xsl:with-param name="new" select="'\r'"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string,'& #x9;')">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="old" select="'& #x9;'"/>
<xsl:with-param name="new" select="'\t'"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:param name="old"/>
<xsl:param name="new"/>
<xsl:call-template name="escape">
<xsl:with-param name="string" select="substring-before($string,$old)"/>
</xsl:call-template>
<xsl:value-of select="$new"/>
<xsl:call-template name="escape">
<xsl:with-param name="string" select="substring-after($string,$old)"/>
</xsl:call-template>
</xsl:template>

</xsl:transform>


Thumbs up for the ABAP Language Group for providing these useful tools!

PS: you have to replace all occurrences of & #x with &#x in the source code above because I couldn't figure out how to dissuade this editor from doing multi-pass unencoding 😞
6 Comments