xsltworkday-api

XSLT : How to create XSLT process Workday report - XML to JSON output - method="json" <xsl:output method="json" />


Created new XSLT process using below XML DATA. When I use output method = "text" below XSLT code working but output method changed to (Method="json") XSLT process failing. my goal generate file/data as JSON.

Note: I am looking generic process. My XSLT code implemented as generic method and NO HARDCODED columns in XSLT code.

-- XML Data

<wd:Report_Data xmlns:wd="urn:com.workday.report/EntryReason">
<wd:Report_Entry>
<wd:EntryReasonRefID>ABC_123</wd:EntryReasonRefID>
<wd:EntryReason>ABC_123 Reimbursement - Non Taxable</wd:EntryReason>
<wd:CreatedMoment>2022-08-30T18:35:34.040-07:00</wd:CreatedMoment>
<wd:LastFunctionallyUpdated>2022-09-01T12:37:48.427-07:00</wd:LastFunctionallyUpdated>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:EntryReasonRefID>ABC_456</wd:EntryReasonRefID>
<wd:EntryReason>ABC_456 - Air Travel</wd:EntryReason>
<wd:CreatedMoment>2022-08-30T18:35:34.370-07:00</wd:CreatedMoment>
<wd:LastFunctionallyUpdated>2022-09-01T12:37:48.552-07:00</wd:LastFunctionallyUpdated>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:EntryReasonRefID>ABC_789</wd:EntryReasonRefID>
<wd:EntryReason>ABC_789 - Car Rental</wd:EntryReason>
<wd:CreatedMoment>2022-08-30T18:35:34.501-07:00</wd:CreatedMoment>
<wd:LastFunctionallyUpdated>2022-09-01T12:37:48.672-07:00</wd:LastFunctionallyUpdated>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:EntryReasonRefID>ABC_999</wd:EntryReasonRefID>
<wd:EntryReason>ABC_999 - Hotel</wd:EntryReason>
<wd:EntryReason>ABC_999 - Rental</wd:EntryReason>
<wd:CreatedMoment>2022-08-30T18:35:34.636-07:00</wd:CreatedMoment>
<wd:LastFunctionallyUpdated>2022-09-01T12:37:48.779-07:00</wd:LastFunctionallyUpdated>
</wd:Report_Entry>
</wd:Report_Data>

============================================ --- XSLT Code

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:wd="urn:com.workday.report/EntryReason" version="3.0">
     <xsl:output method="json" indent="yes"/>
    <xsl:template match="/wd:Report_Data">
    <xsl:text>&#xA0;[&#xa;</xsl:text>
        <xsl:for-each select="wd:Report_Entry">
            <xsl:text>&#xA0;{&#xa;</xsl:text>
            
            <xsl:for-each select="./*"> 
                <xsl:text>&#xA0;</xsl:text>
                <xsl:choose> 
                    <xsl:when test="@wd:Descriptor"><xsl:value-of select="concat('&quot;',local-name(),'&quot;:&quot;',@wd:Descriptor,'&quot;')"/></xsl:when>
                    <xsl:otherwise><xsl:value-of select="concat('&quot;',local-name(),'&quot;:&quot;',.,'&quot;')"/></xsl:otherwise>
                </xsl:choose>
                <xsl:if test="position() != last()">
                    <xsl:text>,&#xa; </xsl:text>
                </xsl:if>
            </xsl:for-each>
             
            <xsl:text>&#xa; &#xA0;}</xsl:text>
            <xsl:if test="position() != last()">
                <xsl:text>,&#xa;</xsl:text>
            </xsl:if>
        </xsl:for-each>
        
        <xsl:text>&#xa;&#xA0;]</xsl:text>
    </xsl:template>
    <xsl:template match="node() | @*"/>
</xsl:stylesheet>

--Error Details :

Error executing XSLT at line 8 : JSON output method cannot handle sequences of two or more items

//Expected Result as output Method = json :

[
    {
        "EntryReasonRefID": "ABC_123 Reimbursement - Non Taxable",
        "EntryReason": "ABC_123",
        "CreatedMoment": "2022-08-30T18:35:34.040-07:00",
        "LastFunctionallyUpdated": "2022-09-01T12:37:48.427-07:00"
    },
    {
        "EntryReasonRefID": "ABC_456 - Air Travel",
        "EntryReason": "ABC_456",
        "CreatedMoment": "2022-08-30T18:35:34.040-07:00",
        "LastFunctionallyUpdated": "2022-09-01T12:37:48.427-07:00"
    },
    {
        "EntryReasonRefID": "ABC_789 - Car Rental",
        "EntryReason": "ABC_789",
        "CreatedMoment": "2022-08-30T18:35:34.040-07:00",
        "LastFunctionallyUpdated": "2022-09-01T12:37:48.427-07:00"
    },
    {
        "EntryReasonRefID": "ABC_999 - Hotel; ABC_999 - Rental",
        "EntryReason": "ABC_999",
        "CreatedMoment": "2022-08-30T18:35:34.040-07:00",
        "LastFunctionallyUpdated": "2022-09-01T12:37:48.427-07:00"
    }
]

Solution

  • You don't say what happens when you run your code, only that it's "failing". You really should report the error message you receive, because it can be helpful to people trying to help you.

    I am guessing it fails because your stylesheet produces as output a sequence of text nodes, and tries to serialize that as JSON, which should produce a serialization error err:SERE0023 because a sequence of more than one item is not a valid JSON document.

    To produce JSON output your stylesheet should do either one of these two things:

    1. generate a sequence of text nodes which represent a serialized JSON object, and use the text output method to convert that sequence to a single string
    2. generate a sequence of one data item which can be represented in JSON (i.e. a single string or number, a map, or an array), and use the json output method to serialize that data structure as JSON.

    You have generated a plain text representation of a serialized JSON object (an array), but you've used the json output method instead of the text output method to serialize it. NB you haven't dealt with escaping text which might contain a " character.

    An alternative and better approach would be generate an array of maps, and serialize that array using the json output method. There are various ways you could do that. One way would be to generate fn:array, fn:map, fn:string etc XML elements and then convert that data structure to JSON using the xml-to-json XPath function. In this case the conversion is simple enough that you can do the whole thing in a single XPath expression:

    <xsl:stylesheet
        xmlns:map="http://www.w3.org/2005/xpath-functions/map"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:wd="urn:com.workday.report/EntryReason" version="3.0">
        <xsl:output method="json" indent="yes"/>
        <xsl:template match="/wd:Report_Data">
            <xsl:sequence select="
                array{
                    for $entry in wd:Report_Entry return map:merge(
                        for $element in $entry/* return map{
                            local-name($element): 
                            string-join(
                              for $value in 
                                $entry/*[local-name()=local-name($element)] 
                              return
                                string($element),
                              '; '
                            )
                        }
                    )
                }
            "/>
        </xsl:template>
    </xsl:stylesheet>
    

    The XPath expression in that xsl:sequence statement means: