Reputation: 1
Below is my input XML which needs to converted into the below output JSON using XSLT 3.0.
with the below XSLT code, it is giving only the values but not the JSON fields name. Can you please help me with this issue, Is xml-to-json() function the correct function get the expected output or is there any other way to get achieve this?
XSLT Code:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/DATA_DS">
<!-- CONVERT INPUT TO XML FOR JSON -->
<xsl:variable name="xml">
<root>
<event>
<id>
<xsl:value-of select="//MessageHeader/SenderParty/InternalID"/>
</id>
<actionCode>DELETE</actionCode>
<type>
<xsl:value-of select="//ShipmentTypeCode"/>
</type>
<timestamp>
<xsl:value-of select="//MessageHeader/CreationDateTime"/>
</timestamp>
<sourceSystemName>
<xsl:value-of select="//MessageHeader/SenderBusinessSystemID"/>
</sourceSystemName>
</event>
<shipment>
<idReferenceType>
<xsl:value-of select="//Plant"/>
</idReferenceType>
<idReferenceNumber>
<xsl:value-of select="//BaseBusinessTransactionDocumentReference/ID/@schemeAgencyID"/>
</idReferenceNumber>
<freightOrderNumber>
<xsl:value-of select="//ShipCancelAddlData/transportHolderNumber"/>
</freightOrderNumber>
<shipmentTypeSystemCode>
<xsl:value-of select="//transportHolderTypeCode"/>
</shipmentTypeSystemCode>
<id>
<xsl:value-of select="//BaseBusinessTransactionDocumentReference/ID/@schemeID"/>
</id>
<shipmentDates>
<shipmentChangeTimestamp>
<xsl:value-of select="substring(//ShipmentMode, 1, 4)"/>-<xsl:value-of select="substring(//ShipmentMode, 5, 2)"/>-<xsl:value-of select="substring(//ShipmentMode, 7, 2)"/>T<xsl:value-of select="substring(//ShipmentMode, 9, 2)"/>:<xsl:value-of select="substring(//ShipmentMode, 11, 2)"/>:<xsl:value-of select="substring(//ShipmentMode, 13, 2)"/>.000Z
</shipmentChangeTimestamp>
</shipmentDates>
<shipmentTexts>
<textTypeCode>
<xsl:value-of select="//shipmentUltimateDestinationCode"/>
</textTypeCode>
<textLine>
<xsl:value-of select="//shipmentUltimateDestinationTypeCode"/>
</textLine>
</shipmentTexts>
</shipment>
</root>
</xsl:variable>
<!-- OUTPUT the JSON -->
<xsl:value-of select="xml-to-json($xml)"/>
</xsl:template>
</xsl:stylesheet>
Input XML:
<TransportationOrderCancellationRequest>
<MessageHeader>
<CreationDateTime>2023-08-08T16:22:33.131Z</CreationDateTime>
<SenderBusinessSystemID>ABC</SenderBusinessSystemID>
<SenderParty>
<InternalID>123</InternalID>
</SenderParty>
<RecipientParty>
<InternalID>XYZ</InternalID>
</RecipientParty>
</MessageHeader>
<TransportationDocument>
<BaseBusinessTransactionDocumentReference>
<ID>234</ID>
<TypeCode>456</TypeCode>
<VersionID>XYZ</VersionID>
</BaseBusinessTransactionDocumentReference>
<ShipCancelAddlData>
<Plant>BIL</Plant>
<ShipmentMode>123</ShipmentMode>
<ShipmentTypeCode>SHIPPED</ShipmentTypeCode>
<shipmentUltimateDestinationCode>GENERAL_TEXT</shipmentUltimateDestinationCode>
<shipmentUltimateDestinationTypeCode>Orders</shipmentUltimateDestinationTypeCode>
<transportHolderNumber>123</transportHolderNumber>
<transportHolderTypeCode>09</transportHolderTypeCode>
</ShipCancelAddlData>
</TransportationDocument>
</TransportationOrderCancellationRequest>
Expected Output:
{
"event": {
"id": "123",
"actionCode": "DELETE",
"type": "SHIPPED",
"timestamp": "2023-08-08T16:22:33.131Z",
"sourceSystemName": "ABC"
},
"shipment": {
"idReferenceType": "BIL",
"idReferenceNumber": "",
"freightOrderNumber": "234",
"shipmentTypeSystemCode": "09",
"id": "",
"shipmentDates": {
"shipmentChangeTimestamp": "2023-08-08T16:22:32.000Z"
},
"shipmentTexts": [
{
"textTypeCode": "GENERAL_TEXT",
"textLine": "Orders"
}
]
}
}
Upvotes: 0
Views: 59
Reputation: 3258
Here's another way which keeps your existing "pipeline" approach of pre-processing the XML into a different XML format, and then converting that to JSON.
As Martin pointed out, the standard xml-to-json
function isn't designed to convert generic XML into JSON, but to convert a specific XML vocabulary corresponding to the low-level JSON syntax, with elements named map
, array
, string
etc.
In this stylesheet I've defined a new function which converts XML elements to JSON by recursively converting container elements into maps, and elements without child elements into strings.
I changed your stylesheet's xsl:output/@method
so that it produced JSON directly, and I changed the final xsl:value-of
to xsl:sequence
so that it would output the JSON map rather than try to convert it to a string (which is what xsl:value-of
does).
There's an additional x:
namespace defined on the xsl:stylesheet
element, because the names of user-defined functions have to be in a namespace. The namespace itself is totally arbitrary.
I wrapped your sample document in a DATA_DS
element, as implied by your main template's match
expression.
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="https://example.com/x"
>
<xsl:output method="json" indent="true"/>
<xsl:function name="x:xml-to-json"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
>
<xsl:param name="element"/>
<xsl:sequence select="
if ($element/*) then
map:merge(
map:for-each(
map:merge(
$element/*/map:entry(
local-name(.),
x:xml-to-json(.)
),
map{'duplicates': 'combine'}
),
function($key, $json-values) {
map:entry(
$key,
if (count($json-values) = 1) then
$json-values
else
array{$json-values}
)
}
)
)
else
string($element)
"/>
</xsl:function>
<xsl:template match="/DATA_DS">
<!-- CONVERT INPUT TO XML FOR JSON -->
<xsl:variable name="xml">
<root>
<event>
<id>
<xsl:value-of select="//MessageHeader/SenderParty/InternalID"/>
</id>
<actionCode>DELETE</actionCode>
<type>
<xsl:value-of select="//ShipmentTypeCode"/>
</type>
<timestamp>
<xsl:value-of select="//MessageHeader/CreationDateTime"/>
</timestamp>
<sourceSystemName>
<xsl:value-of select="//MessageHeader/SenderBusinessSystemID"/>
</sourceSystemName>
</event>
<shipment>
<idReferenceType>
<xsl:value-of select="//Plant"/>
</idReferenceType>
<idReferenceNumber>
<xsl:value-of select="//BaseBusinessTransactionDocumentReference/ID/@schemeAgencyID"/>
</idReferenceNumber>
<freightOrderNumber>
<xsl:value-of select="//ShipCancelAddlData/transportHolderNumber"/>
</freightOrderNumber>
<shipmentTypeSystemCode>
<xsl:value-of select="//transportHolderTypeCode"/>
</shipmentTypeSystemCode>
<id>
<xsl:value-of select="//BaseBusinessTransactionDocumentReference/ID/@schemeID"/>
</id>
<shipmentDates>
<shipmentChangeTimestamp>
<xsl:value-of select="substring(//ShipmentMode, 1, 4)"/>-<xsl:value-of select="substring(//ShipmentMode, 5, 2)"/>-<xsl:value-of select="substring(//ShipmentMode, 7, 2)"/>T<xsl:value-of select="substring(//ShipmentMode, 9, 2)"/>:<xsl:value-of select="substring(//ShipmentMode, 11, 2)"/>:<xsl:value-of select="substring(//ShipmentMode, 13, 2)"/>.000Z
</shipmentChangeTimestamp>
</shipmentDates>
<shipmentTexts>
<textTypeCode>
<xsl:value-of select="//shipmentUltimateDestinationCode"/>
</textTypeCode>
<textLine>
<xsl:value-of select="//shipmentUltimateDestinationTypeCode"/>
</textLine>
</shipmentTexts>
</shipment>
</root>
</xsl:variable>
<!-- OUTPUT the JSON -->
<xsl:sequence select="x:xml-to-json($xml/root)"/>
</xsl:template>
</xsl:stylesheet>
To explain the function: when it's called with an element which contains child elements, it calls itself recursively with each child element, and groups the results by the name of the child element that produced it. Then it returns a map containing a key for each type of child element, with an associated value which is a single item or an array of items, if there were multiple child elements with a given name.
Looking at the result, it doesn't produce quite the result you expect, but this is apparently because the initial stage in your pipeline (which generates the XML to be converted into JSON) doesn't correspond to your sample data, which is apparently missing some attributes.
Upvotes: 1
Reputation: 167716
The approach with xml-to-json
would be like
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2005/xpath-functions"
expand-text="yes">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<!-- CONVERT INPUT TO XML FOR JSON -->
<xsl:variable name="xml">
<map>
<map key="event">
<string key="id">{//MessageHeader/SenderParty/InternalID}</string>
<string key="actionCode">DELETE</string>
</map>
<map key="shipment">
<string key="idReferenceType">{//Plant}</string>
</map>
</map>
</xsl:variable>
<!-- OUTPUT the JSON -->
<xsl:value-of select="xml-to-json($xml, map { 'indent' : true() })"/>
</xsl:template>
</xsl:stylesheet>
where you have to fill in the remaining properties/fields, I have just spelled out some of them so that you can implement the same approach for the remaining properties.
The alternative approach creating/populating XDM 3.1/XPath 3.1 maps and serializing them as JSON is as follows (also only showing a few of your properties as an example so you can fill in the other ones as needed):
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="json" indent="yes"/>
<xsl:template match="/">
<!-- create XDM maps to be serialized as JSON -->
<xsl:sequence
select="map {
'event' : map {
'id' : //MessageHeader/SenderParty/InternalID/string(),
'actionCode' : 'DELETE'
},
'shipment' : map {
'idReferenceType' : //Plant/string()
}
}"/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1