Update: This documentation represents early forms of JSON conventions, Badgerfish and Mapped. GSON support was added a few years later. Moshi support is now included as an alternative to GSON. For users of JSON seeking modern features, see the JSON Support Guide.. For users of JSON and Spring Boot, see the sample application in the JSON and Spring Boot User's Guide.
This document explains the JSON support implementation in Axis2. It includes an introduction to JSON, an outline as to why JSON support is useful to Axis2 and how it should be used. This document also provides details on test cases and samples.
JSON (Java Script Object Notation) is another data exchangeable format like XML, but more lightweight and easily readable. It is based on a subset of the JavaScript language. Therefore, JavaScript can understand JSON, and it can make JavaScript objects by using JSON strings. JSON is based on key-value pairs and it uses colons to separate keys and values. JSON doesn't use end tags, and it uses braces (curly brackets) to enclose JSON Objects.
e.g. <root><test>json object</test></root> == {{json object}}
When it comes to converting XML to JSON and vice versa, there are two major conventions, one named "Badgerfish" and the other, Mapped. The main difference between these two conventions exists in the way they map XML namespaces into JSON.
e.g. <xsl:root xmlns:xsl="http://foo.com"><data>my json string</data></xsl:root>
This XML string can be converted into JSON as follows.
Using Badgerfish
{"xsl:root":{"@xmlns":{"xsl":"http://foo.com"},"data":{"$":"my json string"}}}
Using Mapped
If we use the namespace mapping as http://foo.com -> foo
{"foo.root":{"data":"my json string"}}
Apache Axis2 is a Web services stack that delivers incoming messages into target applications. In most cases, these messages are SOAP messages. In addition, it is also possible to send REST messages through Axis2. Both types of messages use XML as their data exchangeable format. So if we can use XML as a format, why use JSON as another format?
There are many advantages of implementing JSON support in Axis2. Mainly, it helps the JavaScript users (services and clients written in JavaScript) to deal with Axis2. When the service or the client is in JavaScript, it can use the JSON string and directly build JavaScript objects to retrieve information, without having to build the object model (OMElement in Axis2). Also, JavaScript services can return the response through Axis2, just as a JSON string can be shipped in a JSONDataSource.
Other than for that, there are some extra advantages of using JSON in comparison to XML. Although the conversation XML or JSON? is still a hot topic, many people accept the fact that JSON can be passed and built more easily by machines than XML.
At the moment JSON doesn't have a standard and unique content
type. application/json
(this is
the content type which is approved in the JSON RFC),
text/javascript
and
text/json
are some of the commonly
used content types for JSON. Fortunately, in Axis2, the user
has the freedom of specifying the content type to use.
First of all, you need to map the appropriate message formatters and builders to the
content type you are using in the axis2.xml
file. This applies both the to
client side and the server side.
E.g., if you are using the
Mapped convention with the content
type application/json
, add the following declaration:
<messageFormatters> <messageFormatter contentType="application/json" class="org.apache.axis2.json.JSONMessageFormatter"/> <!-- more message formatters --> </messageFormatters> <messageBuilders> <messageBuilder contentType="application/json" class="org.apache.axis2.json.JSONOMBuilder"/> <!-- more message builders --> </messageBuilders>
If you are using the
Badgerfish convention with the
content type text/javascript
, add:
<messageFormatters> <messageFormatter contentType="text/javascript" class="org.apache.axis2.json.JSONBadgerfishMessageFormatter"> <!-- more message formatters --> </messageFormatters> <messageBuilders> <messageBuilder contentType="text/javascript" class="org.apache.axis2.json.JSONBadgerfishOMBuilder"/> <!-- more message builders --> </messageBuilders>
On the client side, make the ConfigurationContext by reading the axis2.xml in which the correct mappings are given.
e.g.
File configFile = new File("test-resources/axis2.xml"); configurationContext = ConfigurationContextFactory .createConfigurationContextFromFileSystem(null, configFile.getAbsolutePath()); .......... ServiceClient sender = new ServiceClient(configurationContext, null);
Set the MESSAGE_TYPE option with exactly the same content type you used in the axis2.xml.
e.g. If you use the content type application/json,
Options options = new Options(); options.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/json"); //more options //................... ServiceClient sender = new ServiceClient(configurationContext, null); sender.setOptions(options);
If you are sending a request to a remote service, you have to know the exact JSON content type that is used by that service, and you have to use that content type in your client as well.
HTTP POST is used as the default method to send JSON messages through Axis2, if the HTTP method is not explicitly set by the user. But if you want to send JSON in HTTP GET method as a parameter, you can do that by just setting an option on the client side.
e.g.
options.setProperty(Constants.Configuration.HTTP_METHOD,
Constants.Configuration.HTTP_METHOD_GET);
Here, the Axis2 receiving side (JSONOMBuilder) builds the OMElement by reading the JSON string which is sent as a parameter. The request can be made even through the browser.
e.g. Sample JSON request through HTTP GET. The JSON message is encoded and sent.
GET
/axis2/services/EchoXMLService/echoOM?query=%7B%22echoOM%22:%7B%22data%22:%5B%22my%20json%20string%22,%22my%20second%20json%20string%22%5D%7D%7D
HTTP/1.1
Since Badgerfish defines a 1-to-1 transformation between JSON and XML, no additional configuration is required on the server side if that convention is used. Any service deployed into Axis2 will work out of the box.
On the other hand, if the Mapped JSON convention is used, then Axis2 needs to know the mappings
between XML namespaces and JSON "namespaces" in order to translate messages from JSON
into XML representations and vice-versa. To use the Mapped convention with a service deployed into Axis2,
add a xmlToJsonNamespaceMap
property with these mappings to the services.xml
file for that service, as
shown in the following example:
<service name="..."> ... <parameter name="xmlToJsonNamespaceMap"> <mappings> <mapping xmlNamespace="http://example.org/foo" jsonNamespace=""/> <mapping xmlNamespace="http://example.org/bar" jsonNamespace="bar"/> </mappings> </parameter> ... </service>
The Axis2 architecture is based on the assumption that any message flowing through
the Axis2 runtime is representable as a SOAP infoset, i.e. as XML wrapped in a SOAP
envelope. Conceptually, the two message builders JSONOMBuilder
and
JSONBadgerfishOMBuilder
convert incoming messages from JSON to XML and
the two message formatters JSONMessageFormatter
and JSONBadgerfishMessageFormatter
convert outgoing messages from XML to JSON. Axis2 doesn't implement its own JSON parser and serializer, and
instead relies on Jettison to do the JSON<->XML conversions.
On the server side the XML for an incoming message is typically converted to Java objects by a databinding (such as ADB or JAX-WS) before the invocation of the service implementation. In the same way, the Java object returned by the service implementation is converted to XML. In the case we are interested in, that XML is then converted by the message formatters to JSON. The usage of an intermediate XML representation is the reason why JSON can be enabled on any service deployed in Axis2.
It is important to note that the explanation given in the previous two paragraphs is only valid from a conceptual point of view. The actual processing model is more complicated. In the next two sections we will explain in detail how Axis2 processes incoming and outgoind JSON messages.
Axis2 relies on Apache Axiom as its XML object model. Although
Axiom has a DOM like API, it also has several advanced features that enable Axis2 to avoid
building a complete object model representation of the XML message. This is important for performance
reasons and distinguishes Axis2 from previous generation SOAP stacks. To leverage these features, the
JSON message builders create a SOAP envelope the body of which contains a single OMSourcedElement
.
An OMSourcedElement
is a special kind of OMElement
that wraps an arbitrary
Java object that can be converted to XML in a well defined way. More precisely, the Java object as well as the logic
to convert the object to XML are encapsulated in an OMDataSource
instance and it is that
OMDataSource
instance that is used to create the OMSourcedElement
.
For JSON, the OMDataSource
implementation is JSONDataSource
or JSONBadgerfishDataSource
,
depending on the convention being used. The base class (AbstractJSONDataSource
) of these two classes
actually contains the code that invokes Jettison to perform the JSON to XML conversion.
An OMSourcedElement
still behaves like a normal OMElement
. In particular, if the
element is accessed using DOM like methods, then Axiom will convert the data encapsulated by
the OMDataSource
on the fly to an object model representation. This process is called expansion of the
OMSourcedElement
. However, the OMDataSource
API is designed such that the conversion to
XML is always done using a streaming API: either the OMDataSource
produces an XMLStreamReader
instance from which the XML representation can be read (this is the case for JSON and the XMLStreamReader
implementation
is actually provided by Jettison) or it serializes the XML representation to an XMLStreamWriter
.
Because of this, expansion of the OMSourcedElement
is often not necessary, so that the overhead of
creating an object model representation can usually be avoided. E.g. a databinding will typically consume the message by requesting an
XMLStreamReader
for the element in the SOAP body, and this doesn't require expansion of the
OMSourcedElement
. In this case, the databinding pulls the XML data almost directly from the
underlying Jettison XMLStreamReader
and no additional Axiom objects are created.
Actually here again, things are slightly more complicated because in order to dispatch to the right
operation, Axis2 needs to determine the name of the element in the body. Since the name is not known
in advance, that operation requires expansion of the OMSourcedElement
. However, at this point
none of the children of the OMSourcedElement
will be built. Fortunately the databindings
generally request the XMLStreamReader
with caching turned off, so that the child nodes will never be
built. Therefore the conclusion of the previous paragraph remains valid: processing the message with a databinding
will not create a complete object model representation of the XML.
Usage of an OMSourcedElement
also solves another architectural challenge posed by
the Mapped JSON convention: the JSON payload can only be converted to XML if the namespace mappings
are known. Since they are defined per service, they are only known after the incoming message has been
dispatched and the target service has been identified. This typically occurs
in RequestURIBasedDispatcher
, which is executed after
the message builder. This means that JSONOMBuilder
cannot actually perform the conversion.
Usage of an OMSourcedElement
avoids this issue because the conversion is done lazily when
the OMSourcedElement
is first accessed, and this occurs after RequestURIBasedDispatcher
has been executed.
Another advantage of using OMSourcedElement
is that a JSON aware service could directly process
the JSON payload without going through the JSON to XML conversion. That is possible because the OMDataSource
simply keeps a reference to the JSON payload and this reference is accessible to JSON aware code.
For outgoing messages, the two JSON message formatters JSONMessageFormatter
and
JSONBadgerfishMessageFormatter
use Jettision to create an appropriate XMLStreamWriter
and then request Axiom to serialize the body element to that XMLStreamWriter
. If a databinding
is used, then the body element will typically be an OMSourcedElement
with an OMDataSource
implementation specific to that databinding. OMSourcedElement
will delegate the serialization
request to the appropriate method defined by OMDataSource
. This means that the databinding code
directly writes to the XMLStreamWriter
instance provided by Jettision, without building an
intermediate XML object model.
Before doing this, the JSON message formatters actually check if the element is an OMSourcedElement
backed by a corresponding JSON OMDataSource
implementation. If that is the case, then they will
extract the JSON payload and directly write it to the output stream. This allows JSON aware services to
bypass the XML to JSON conversion entirely.
The JSON integration test is available under test in the json module of Axis2. It uses the SimpleHTTPServer to deploy the service. A simple echo service is used to return the incoming OMSourcedElement object, which contains the JSONDataSource. There are two test cases for two different conventions and another one test case to send the request in GET.
This sample is available in the samples module of Axis2. It is a client which calls the Yahoo search API using the GET method, with the parameter output=json. The Yahoo search service sends the response as a formatted JSON string with the content type text/javascript. This content type is mapped with the JSONOMBuilder in the axis2.xml. All the results are shown in a GUI. To run the sample, execute the ant script.
These two applications provide good examples of using JSON within Axis2. By reviewing these samples, you will be able to better understand Axis2's JSON support implementation.
To illustrate how JSON can be enabled on an existing service deployed in Axis2,
we will use the ADB stock quote service sample from the
Quick Start Guide. The code for this sample
can be found in the samples/quickstartadb
folder in the binary distribution.
Only a few steps are necessary to enable JSON (using the Mapped convention) on that service:
Configure the JSON message builders and formatters in conf/axis2.xml
.
Add the following element to the messageFormatters
:
<messageFormatter contentType="application/json" class="org.apache.axis2.json.JSONMessageFormatter"/>
Also add the following element to the messageBuilders:
<messageBuilder contentType="application/json" class="org.apache.axis2.json.JSONOMBuilder"/>
Edit the services.xml
for the stock quote service and add the following
configuration:
<parameter name="xmlToJsonNamespaceMap"> <mappings> <mapping xmlNamespace="http://quickstart.samples/xsd" jsonNamespace=""/> </mappings> </parameter>
The services.xml
file can be found under
samples/quickstartadb/resources/META-INF
.
Build and deploy the service by executing the ant script in
samples/quickstartadb
and then start the Axis2 server using
bin/axis2server.sh
or bin/axis2server.bat
.
That's it; the stock quote service can now be invoked using JSON. This can be tested using the well known curl tool:
curl -H 'Content-Type: application/json' -d '{"getPrice":{"symbol":"IBM"}}' http://localhost:8080/axis2/services/StockQuoteService
This will give the following result:
{"getPriceResponse":{"return":42}}