Pretty-Printing JSON with CALL TRANSFORMATION
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() > 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() > 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() > 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() > 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() > 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,'"')">
<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,'\')">
<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 🙁
Thank you so much.
I solved my JSON problem, kar sslc results
I found it is an useful post for me.
Thanks so much for sharing. Exactly what I was hoping to do and works like a charm.
Thanks a lot; very good
This has to be one of the most useful blog on SCN 🙂
Best regards, Abinash
Few changes needed after copy-pasting,
A: ">" should be replaced with ">"
B: "& #xA;" are hex escaped HTML values, and should not contain spaces, ie replace with " "
There's also the built-in options in SXML, see jsonPrettyPrinter by Andre. Shortest possible code (debug to see the result) - 1 character indentation per level - all JSON & code below credited to Andre blog post:
(Lars Hvam fyi if needed)