Warning |
---|
This page is DEPRECATED, please refer to the new source http://struts.apache.org/plugins/json/ |
Table of Contents |
---|
The JSON plugin
Excerpt |
---|
provides a "json" result type that serializes actions into JSON |
...
Given this JSON string:
Code Block |
---|
{
"doubleValue": 10.10,
"nestedBean": {
"name": "Mr Bean"
},
"list": ["A", 10, 20.20, {
"firstName": "El Zorro"
}],
"array": [10, 20]
}
|
The action must have a "setDoubleValue" method, taking either a "float" or a "double" argument (the interceptor will convert the value to the right one). There must be a "setNestedBean" whose argument type can be any class, that has a "setName" method taking as argument an "String". There must be a "setList" method that takes a "List" as argument, that list will contain: "A" (String), 10 (Long), 20.20 (Double), Map ("firstName" -> "El Zorro"). The "setArray" method can take as parameter either a "List", or any numeric array.
Installation
This plugin can be installed by copying the plugin jar into your application's /WEB-INF/lib
directory. No other files need to be copied or created.
Tip |
---|
So serialize your objects to JSON in javascript see json2. |
Note |
---|
|
Tip |
---|
This plugin also provides AJAX Validation. |
Installation
This plugin can be installed by copying the plugin jar into your application's /WEB-INF/lib
directory. No other files need to be copied or created.
To use maven, add this to your pom:
Code Block | ||||
---|---|---|---|---|
| ||||
<dependencies> ... <dependency> <groupId>com<groupId>org.apache.googlecode<struts</groupId> <artifactId>jsonplugin<<artifactId>struts2-json-plugin</artifactId> <version>0.26<<version>STRUTS_VERSION</version> </dependency> ... </dependencies> <repository> <id>Maven Plugin Repository</id> <url>http://struts2plugin-maven-repo.googlecode.com/svn/trunk/</url> <snapshots> <enabled>false</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> |
Customizing Serialization and Deserialization
Use the JSON annotation to customize the serialization/deserialization process. Available JSON annotation fields:
Customizing Serialization and Deserialization
Use the JSON annotation to customize the serialization/deserialization process. Available JSON annotation fields:
Name | Description | Default Value | Serialization | Deserialization |
---|---|---|---|---|
name | Customize field name | |||
Name | Description | Default Value | Serialization | Deserialization |
name | Customize field name | empty | yes | no |
serialize | Include in serialization | true | yes | no |
deserialize | Include in deserialization | true | no | yes |
format | Format used to format/parse a Date field | "yyyy-MM-dd'T'HH:mm:ss" | yes | yes |
...
A comma-delimited list of regular expressions can be passed to the JSON Result and Interceptor, properties matching any of these regular expressions will be ignored on the serialization process:
Code Block | ||||
---|---|---|---|---|
| ||||
<!-- Result fragment -->
<result type="json">
<param name="excludeProperties">
login.password,
studentList.*\.sin
</param>
</result>
<!-- Interceptor fragment -->
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="excludeProperties">
login.password,
studentList.*\.sin
</param>
</interceptor-ref>
|
...
Note | ||
---|---|---|
| ||
Exclude property expressions take precedence over include property expressions. That is, if you use include and exclude property expressions on the same result, include property expressions will not be applied if an exclude exclude property expression matches a property first. |
Code Block | ||||
---|---|---|---|---|
| ||||
<!-- Result fragment -->
<result type="json">
<param name="includeProperties">
^entries\[\d+\]\.clientNumber,
^entries\[\d+\]\.scheduleNumber,
^entries\[\d+\]\.createUserId
</param>
</result>
|
...
Use the "root" attribute(OGNL expression) to specify the root object to be serialized.
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="root">
person.job
</param>
</result>
|
The "root" attribute(OGNL expression) can also be used on the interceptor to specify the object that must be populated, make sure this object is not null.
Code Block | ||||
---|---|---|---|---|
| ||||
<interceptor-ref name="json">
<param name="root">bean1.bean2</param>
</interceptor-ref>
|
...
For several reasons you might want to wrap the JSON output with some text, like wrapping with comments, adding a prefix, or to use file uploads which require the result to be wrapped in a textarea. Use wrapPrefix to add content in the beginning and wrapPostfix to add content at the end. This settings take precedence over "wrapWithComments" and "prefix" which are deprecated from 0.34 on. Examples:
Wrap with comments:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="wrapPrefix">/*</param>
<param name="wrapSuffix">*/</param>
</result>
|
Add a prefix:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="wrapPrefix">{}&&</param>
</result>
|
Wrap for file upload:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="wrapPrefix"><![CDATA[<html><body><textarea>]]></param>
<param name="wrapSuffix"><![CDATA[</textarea></body></html>]]></param>
</result>
|
...
If the "wrapWithComments" (false by default) attribute is set to true, the generated JSON is wrapped with comments like:
Code Block |
---|
/* {
"doubleVal": 10.10,
"nestedBean": {
"name": "Mr Bean"
},
"list": ["A", 10, 20.20, {
"firstName": "El Zorro"
}],
"array": [10, 20]
} */
|
To strip those comments use:
Code Block |
---|
var responseObject = eval("("+data.substring(data.indexOf("\/\*")+2, data.lastIndexOf("\*\/"))+")");
|
...
If the parameter prefix is set to true, the generated JSON will be prefixed with "{}&& ". This will help prevent hijacking. See this Dojo Ticket for details:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="prefix">true</param>
</result>
|
...
By default properties defined on base classes of the "root" object won't be serialized, to serialize properties in all base classes (up to Object) set "ignoreHierarchy" to false in the JSON result:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="ignoreHierarchy">false</param>
</result>
|
...
By default, an Enum is serialized as a name=value pair where value = name().
Code Block |
---|
public enum AnEnum {
ValueA,
ValueB
}
JSON: "myEnum":"ValueA"
|
Use the "enumAsBean" result parameter to serialize Enum's as a bean with a special property _name with value name(). All properties of the enum are also serialized.
Code Block |
---|
public enum AnEnum {
ValueA("A"),
ValueB("B");
private String val;
public AnEnum(val) {
this.val = val;
}
public getVal() {
return val;
}
}
JSON: myEnum: { "_name": "ValueA", "val": "A" }
|
Enable this parameter through struts.xml:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="enumAsBean">true</param>
</result>
|
...
Set the enableGZIP attribute to true to gzip the generated json response. The request must include "gzip" in the "Accept-Encoding" header for this to work.
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="enableGZIP">true</param>
</result>
|
...
- Cache-Control: no-cache
- Expires: 0
- Pragma: No-cache
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="noCache">true</param>
</result>
|
...
By default fields with null values are serialized like {property_name: null}. This can be prevented by setting excludeNullProperties to true.
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="excludeNullProperties">true</param>
</result>
|
...
Use statusCode to set the status of the response:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="statusCode">304</param>
</result>
|
And errorCode to send an error(the server might end up sending something to the client which is not the serialized JSON):
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="errorCode">404</param>
</result>
|
...
To enable JSONP, set the parameter callbackParameter in either the JSON Result or the Interceptor. A parameter with that name will be read from the request, and it value will be used as the JSONP function. Assuming that a request is made with the parameter "callback"="exec":
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="callbackParameter">callback</param>
</result>
|
...
Content type will be set to application/json-rpc by default if SMD is being used, or application/json otherwise. Sometimes it is necessary to set the content type to something else, like when uploading files with Dojo and YUI. Use the contentType parameter in those cases.
Code Block | XML | XML |
---|
<result type="json">
<param name="contentType">text/html</param>
</result>
|
Example
Setup Action
This simple action has some fields:
Example:
| ||||
<result type="json">
<param name="contentType">text/html</param>
</result>
|
Encoding
User can define encoding per result or base on default assigned to struts.i18n.encoding. To define encoding for given result add encoding param as below:
Code Block | ||||
---|---|---|---|---|
| ||||
<result type="json">
<param name="encoding">UTF-8</param>
</result>
|
Example
Setup Action
This simple action has some fields:
Example:
Code Block | ||||
---|---|---|---|---|
| ||||
import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.Action;
public class JSONExample {
private String field1 = "str";
private int[] ints = {10, 20};
private Map map = new HashMap();
private String customName = "custom";
//'transient' fields are not serialized
private transient String field2;
//fields without getter method are not serialized
private String field3;
public String execute() {
map.put("John", "Galt");
return Action.SUCCESS;
}
public String getField1() | ||||
Code Block | ||||
JAVA | JAVA | import java.util.HashMap; import java.util.Map; import com.opensymphony.xwork2.Action; public class JSONExample { private Stringreturn field1; = "str"; } privatepublic int[] ints = {10, 20}void setField1(String field1) { this.field1 = field1; } private Map mappublic = new HashMapint[] getInts(); { private String customName =return "custom"ints; //'transient' fields are not serialized private transient String field2; //fields without getter method are not serialized private String field3;} public void setInts(int[] ints) { this.ints = ints; } public StringMap executegetMap() { map.put("John", "Galt"); return Action.SUCCESSreturn map; } public Stringvoid getField1setMap(Map map) { return field1this.map = map; } @JSON(name="newName") public void setField1(String field1getCustomName() { return this.field1 = field1customName; } public int[] getInts() { return ints; } public void setInts(int[] ints) { this.ints = ints; } public Map getMap() { return map; } public void setMap(Map map) { this.map = map; } @JSON(name="newName") public String getCustomName() { return this.customName; } } |
Write the mapping for the action
- Add the map inside a package that extends "json-default"
- Add a result of type "json"
Example:
...
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" extends="json-default">
<action name="JSONExample" class="example.JSONExample">
<result type="json"/>
</action>
</package>
</struts>
}
|
Write the mapping for the action
- Add the map inside a package that extends "json-default"
- Add a result of type "json"
Example with Convention Plugin Configuration:
Code Block | ||||
---|---|---|---|---|
| ||||
import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.Result;
@Result(type = "json")
public class JSONExample extends ActionSupport {
// action code
}
|
Example with XML Configuration:
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" extends="json-default">
<action name="JSONExample" class="example.JSONExample">
<result type="json"/>
</action>
</package>
</struts>
|
JSON example output
Code Block |
---|
{
"field1" : "str",
"ints": [10, 20],
"map": {
"John":"Galt"
},
"newName": "custom"
}
|
Accepting JSON
Your actions can accept incoming JSON if they are in package which uses json
interceptor or by adding reference to it as follow:
Code Block |
---|
@InterceptorRef(value="json") |
By default Content-Type
of value application/json
is recognised to be used for de-serialisation and application/json-rpc
to execute SMD processing. You can override those settings be defining jsonContentType
and jsonRpcContentType
params, see example:
Code Block | ||||
---|---|---|---|---|
| ||||
<interceptor-ref name="json">
<param name="jsonContentType">text/json</param>
<param name="jsonRpcContentType">text/json-rpc</param>
</interceptor-ref> |
Please be aware that those are scoped params per stack, which means, once set it will be used by actions in scope of this stack.
JSON example output
...
JSON RPC
The json plugin can be used to execute action methods from javascript and return the output. This feature was developed with Dojo in mind, so it uses Simple Method Definition to advertise the remote service. Let's work it out with an example(useless as most examples).
First write the action:
Code Block | ||||
---|---|---|---|---|
| ||||
package smd;
import com.googlecode.jsonplugin.annotations.SMDMethod;
import com.opensymphony.xwork2.Action;
public class SMDAction {
public String smd() {
return Action.SUCCESS;
}
@SMDMethod
public Bean doSomething(Bean bean, int quantity) {
bean.setPrice(quantity * 10);
return bean;
}
}
|
...
The bean class:
Code Block | ||||
---|---|---|---|---|
| ||||
package smd;
public class Bean {
private String type;
private int price;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
|
The mapping:
Code Block | ||||
---|---|---|---|---|
| ||||
<package name="RPC" namespace="/nodecorate" extends="json-default">
<action name="SMDAction" class="smd.SMDAction" method="smd">
<interceptor-ref name="json">
<param name="enableSMD">true</param>
</interceptor-ref>
<result type="json">
<param name="enableSMD">true</param>
</result>
</action>
</package>
|
...
Now the javascript code:
Code Block |
---|
<s:url id="smdUrl" namespace="/nodecorate" action="SMDAction" />
<script type="text/javascript">
//load dojo RPC
dojo.require("dojo.rpc.*");
//create service object(proxy) using SMD (generated by the json result)
var service = new dojo.rpc.JsonService("${smdUrl}");
//function called when remote method returns
var callback = function(bean) {
alert("Price for " + bean.type + " is " + bean.price);
};
//parameter
var bean = {type: "Mocca"};
//execute remote method
var defered = service.doSomething(bean, 5);
//attach callback to defered object
defered.addCallback(callback);
</script>
|
Dojo's JsonService will make a request to the action to load the SMD, which will return a JSON object with the definition of the available remote methods, using that information Dojo creates a "proxy" for those methods. Because of the asynchronous nature of the request, when the method is executed, a deferred object is returned, to which a callback function can be attached. The callback function will receive as a parameter the object returned from your action. That's it.
Proxied objects
(V0.20) As annotations are not inherited in Java, some user might experience problems while trying to serialize objects that are proxied. eg. when you have attached AOP interceptors to your action.
...
NOTE: This parameter should only be set to false if your action could be a proxy as there is a performance cost caused by recursion through the interfaces.
Code Block |
---|
<action name="contact" class="package.ContactAction" method="smd">
<interceptor-ref name="json">
<param name="enableSMD">true</param>
<param name="ignoreSMDMethodInterfaces">false</param>
</interceptor-ref>
<result type="json">
<param name="enableSMD">true</param>
<param name="ignoreInterfaces">false</param>
</result>
<interceptor-ref name="default"/>
</action>
|