Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Introduce API to convert the Ballerina record to XML #2819

Closed
kalaiyarasiganeshalingam opened this issue Mar 28, 2022 · 14 comments · Fixed by ballerina-platform/module-ballerina-xmldata#432
Assignees
Labels
module/xmldata Status/Accepted Accepted proposals Team/DIU Data, IO, and Util packages related issues Type/Proposal
Milestone

Comments

@kalaiyarasiganeshalingam
Copy link
Contributor

kalaiyarasiganeshalingam commented Mar 28, 2022

Summary

The Ballerina Xmldata module doesn't have any API to convert the Ballerina record to XML directly. So, this proposal introduces a new fromRecord API to convert the Ballerina record to XML.

Goals

Provide a way to directly convert the Ballerina record to XML.

Motivation

When we are writing a connector for the SOAP backend service(e.g Netsuite Connector), we need to convert the Ballerina record value to XML payload. As mentioned in the summary, We don't have a way to convert the Ballerina record to XML. At the moment, users have to write their own custom implementation to convert Ballerina records to XML and it would be easy for them If we provide an API to convert.

Note: This feature is also required by the connector team and they are planning to remove their custom implementation and use xmldata module, once we have this feature.

Description

The API definition of this:

# Converts a Ballerina record to an XML representation.
# ```ballerina
# Employee data = {
#   name: "Asha"
# };
# xml? xmlValue = check xmldata:fromRecord(data);
# ```
#
# + recordValue - The Ballerina record to be converted to XML
# + arrayEntryTag - The name of the XML elements that represent 
#                   a converted JSON array entry when the JSON 
#                   array entry key is not present during the conversion
# + return - An XML representation of the given Ballerina record if the record is
#            successfully converted or else an `xmldata:Error`
public isolated function fromRecord(record {} recordValue, string arrayEntryTag = "item") returns xml|Error;

Rules for Record to XML Conversion

The following rules are used during the conversion process:

  • A default root element will be created When data contains multiple key-value pairs
    Person data = {
         fname: "John",
         lname: "Stallone"
     };
  • If JSON properties' keys have the prefix as _, those will be handled as attributes or namespaces in the XML.
  • If optional values in the Ballerina record are nil, those will be converted to empty XML elements in the XML.
    Person data = {
         fname: "John",
         lname: ()
     };
  • If optional fields are in the Ballerina record, those will be skipped.

The following table shows a mapping between the different forms of XML, to a corresponding matching Ballerina record representation by considering the above rules.

Record Type Record Sample XML Representation Type XML Representation of XML
Record has single
key-value
{name:"Asha"} Empty element <name>Asha</name>
Record has single
key-value and value is ""
{name:""} Empty element <name/>
Record has optional values {name: ()} Empty element <name/>
Empty record {} Empty Sequence ``
Record with
single key-value
{
  "store": {
    "name": "Anne",
    "address": {
     "street": "Main",
     "city": "94"
    }
  }
}
XML sequence <store>
  <name>Anne</name>
  <address>
    <street>Main</street>
    <city>94</city>
  </address>
</store>
Record with
multiple key-value pairs
{
   "key1":"value1",
   "key2":"value2"
}
XML sequence with root tag <root>
  <key1>value1</key1>
  <key2>value2</key2>
</root>
Record with key
as "\#content"
{"\#content":"value1"} XML text value1
Record with key
prefix as ‘_’
{
  "foo": {
    "_key": "value",
    "_xmlns\:ns0":"<http://sample.com>"
  }
}
XML element with attribute and namespace <foo key="value"
xmlns:ns0="<http://sample.com>"/>
@daneshk
Copy link
Member

daneshk commented Mar 28, 2022

@kalaiyarasiganeshalingam How are we handling optional values in the Ballerina record. are we skipping those fields. And also shall we mention, how XML namespaces are handled. other special scenarios in the proposal

@daneshk daneshk closed this as completed Mar 28, 2022
@daneshk daneshk reopened this Mar 28, 2022
@kalaiyarasiganeshalingam
Copy link
Contributor Author

kalaiyarasiganeshalingam commented Mar 28, 2022

@kalaiyarasiganeshalingam How are we handling optional values in the Ballerina record. are we skipping those fields.

No, we have to convert to an empty XML element, but if the record has optional fields, we can skip those. I have added these to the proposal now.

And also shall we mention, how XML namespaces are handled. other special scenarios in the proposal

Updated the proposal

@daneshk
Copy link
Member

daneshk commented Mar 30, 2022

@sameerajayasoma would you please have a look and give your feedback.

@jclark
Copy link

jclark commented Apr 29, 2022

This should work consistently with OpenAPI

https://swagger.io/docs/specification/data-models/representing-xml/

If I have a value of type json (including records) that belongs to some Ballerina type T that we map to some OpenAPI spec S, and there is a value V that belongs to T, then the mapping of V to X being done in this issue should be the valid XML according to S.

@daneshk daneshk added the Team/DIU Data, IO, and Util packages related issues label May 11, 2022
@sameerajayasoma
Copy link
Contributor

The default attribute prefix in xmldata:fromJson() method is @, but as per this proposal the attribute prefix of xmldata:fromRecord is _. Can you explain why?

In Ballerina json is a subtype of record {|anydata...;|}. Why do we need two functions fromJson() and fromRecord. Can't we have a single function for this?

Can we introduce annotations to change the default behavior of this record to XML mapping and vice versa? OpenAPI spec allow adding fields to change the behaviour.

@sameerajayasoma
Copy link
Contributor

The proposal does not cover records values having fields of type table and xml. A table value can be mapped to an array of corresponding xml elements.

@daneshk daneshk added this to the 2201.2.0 milestone May 12, 2022
@daneshk
Copy link
Member

daneshk commented May 17, 2022

No, we don't have actual users requesting this functionality.

But, the Ballerina connector team is building a WSDL tool that builds client connectors to connect to the SOAP backends. This feature is useful for them to convert Ballerina records to XML payload easily. Currently, they are building the XML payload from the record in each connector manually.

@daneshk
Copy link
Member

daneshk commented May 19, 2022

@jclark @sameerajayasoma Could you please give your feedback on the updated changes.

@jclark
Copy link

jclark commented May 23, 2022

I guess I don't really understand what problem you are trying to solve: fromRecord doesn't make much sense to me. There are lots of different ways of converting between Ballerina and XML: there's no one right way; it depends what problem you are trying to solve.

I would see OpenAPI-style XML as an alternative way of serializing the json and anydata types. So I would expect a

function toXml(map<anydata)>) returns xml;
function fromXml(xml, typedesc<map<anydata>> t  = <>) returns error|t;

@daneshk
Copy link
Member

daneshk commented May 23, 2022

The problem is, We are building a connector to connect with the SOAP backend where the request payload is XML type. The connector APIs accept Ballerina record values, and inside the connector, we need to convert the Ballerina record value to XML value and construct a request payload.

We are planning to give an API from the xmldata module to convert Ballerina value to XML To be used in those connectors. We went through OpenAPI-style and amended our design by introducing a few annotations to mark XML attributes and namespaces.

As we already have xmldata:fromJson and xmldata:toJson functions which are specific to JSON <> XML conversion. So we thought of calling the functions of Ballerina record value conversions like xmldata:fromRecord and xmldata:ToRecord. since JSON is also a subtype of record {|anydata...;|}, we don't need two APIs.

+1 for suggested APIs. They can use to convert both JSON and anydata types—the only doubt is whether the function names are aligned with our convention. xmldata:toXML and xmldata:fromXML

@kalaiyarasiganeshalingam
Copy link
Contributor Author

Please find the following APIs definitions according to the latest suggestion.

# Converts an XML to its `Map` or `Record` representation.
# The namespaces and attributes will not be considered a special case.
#
# + xmlValue - The XML source to be converted to a given target type
# + returnType - The `typedesc` of the `map<anydata>` that should be returned as a result
# + return - The given target type representation of the given XML on success,
# else returns an `xmldata:Error`
public isolated function fromXml(xml xmlValue, typedesc<(map<anydata>|json)> returnType = <>)
returns returnType|Error;
# Converts a `Map` or `Record` representation to its XML representation.
# The record has annotations to configure namespaces and attributes,  but others don't have these.
#
# + mapValue - The `Map` or `Record` representation source to be converted to XML
# + return - XML representation of the given source if the source is
# successfully converted or else an `xmldata:Error`
public isolated function toXml(map<anydata> mapValue) returns xml|Error;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module/xmldata Status/Accepted Accepted proposals Team/DIU Data, IO, and Util packages related issues Type/Proposal
Projects
No open projects
Status: StandardLibrary-DUI
Development

Successfully merging a pull request may close this issue.

4 participants