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
Former Member
Thank you so much.

I solved my JSON problem, kar sslc results

I found it is an useful post for me.

 
m_olson
Explorer
0 Kudos
Thanks so much for sharing. Exactly what I was hoping to do and works like a charm.
0 Kudos

Thanks a lot; very good

AbinashNanda
Product and Topic Expert
Product and Topic Expert
0 Kudos
This has to be one of the most useful blog on SCN 🙂

Best regards, Abinash

 
larshp
Active Contributor
0 Kudos
Few changes needed after copy-pasting,

A: "&gt;" should be replaced with ">"

B: "& #xA;" are hex escaped HTML values, and should not contain spaces, ie replace with " "
Sandra_Rossi
Active Contributor

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:

    DATA(json_string) = `{ "firstName": "John", "lastName": "Smith", "isAlive": true, "age": 27,` && |\r\n|  &&
` "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY",` && |\r\n| &&
` "postalCode": "10021-3100" }, "phoneNumbers": [ { "type": "home",` && |\r\n| &&
` "number": "212 555-1234" }, { "type": "office", "number": "646 555-4567" } ],` && |\r\n| &&
` "children": [], "spouse": null }`.
DATA(reader) = cl_sxml_string_reader=>create( cl_abap_codepage=>convert_to( json_string ) ).
DATA(writer) = CAST if_sxml_writer( cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ) ).
writer->set_option( option = if_sxml_writer=>co_opt_linebreaks ).
writer->set_option( option = if_sxml_writer=>co_opt_indent ).
reader->next_node( ).
reader->skip_node( writer ).
DATA(formatted_json_string) = cl_abap_codepage=>convert_from( CAST cl_sxml_string_writer( writer )->get_output( ) ).

(lars.hvam fyi if needed)