Name | JSON Plugin | ||
---|---|---|---|
Publisher | Musachy Barroso Apache Struts | ||
License | Open Source (ASL2) | ||
Version | 0.20 | ||
Compatibility | Struts 2.01.6 or later | ||
Homepage | http://codecwiki.googleapache.comorg/pconfluence/jsonplugin/ | Download | http://code.google.com/p/jsonplugin/downloads/list display/WW/JSON+Plugin |
Wiki Markup |
---|
{rate:title=Rating|theme=dynamic} |
Warning |
---|
The JSON plugin is bundled with Struts since 2.1.7+. The plugin at Google Code has been deprecated. |
Overview
The JSON plugin
Excerpt |
---|
provides a "json" result type that serializes actions into JSON |
- The "content-type" must be "application/json"
- The JSON content must be well formed, see json.org for grammar.
- Action must have a public "setter" method for fields that must be populated.
- Supported types for population are: Primitives (int,long...String), Date, List, Map, Primitive Arrays, Other class (more on this later), and Array of Other class.
- Any object in JSON, that is to be populated inside a list, or a map, will be of type Map (mapping from properties to values), any whole number will be of type Long, any decimal number will be of type Double, and any array of type List.
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.
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 | 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 expression can be passed to the JSON Result and Interceptor, properties matching any of these regular expression will be ignored on the serialization process:
...
<!-- 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>
Root Object
Use the "root" attribute(OGNL expression) to specify the root object to be serialized.
...
<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.
...
<interceptor-ref name="json">
<param name="root">bean1.bean2</param>
</interceptor-ref>
Wrap with Comments
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]
} */
|
This can be used to avoid potential Javascript Hijacks. To strip those comments use:
Code Block |
---|
var responseObject = eval("("+data.substring(data.indexOf("\/\*")+2, data.lastIndexOf("\*\/"))+")");
|
Base Classes
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:
...
<result type="json">
<param name="ignoreHierarchy">false</param>
</result>
Enumerations
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:
...
<result type="json">
<param name="enumAsBean">true</param>
</result>
Compressing the output. 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.
...
<result type="json">
<param name="enableGZIP">true</param>
</result>
Example
Setup Action
This simple action has some fields:
Example:
...
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() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
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
...
of
...
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>
JSON example output
Code Block |
---|
{
"field1" : "str",
"ints": [10, 20],
"map": {
"John":"Galt"
},
"newName": "custom"
}
|
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:
...
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;
}
}
Methods that will be called remotely must be annotated with the SMDMethod annotation, for security reasons. The method will take a bean object, modify its price and return it. The action can be annotated with the SMD annotation to customize the generated SMD (more on that soon), and parameters can be annotated with SMDMethodParameter. As you can see, we have a "dummy", smd method. This method will be used to generate the Simple Method Definition (a definition of all the services provided by this class), using the "json" result.
The bean class:
...
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:
...
interceptor
...
Nothing special here, except that both the interceptor and the result must be applied to the action, and "enableSMD" must be enabled for both.
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.name + " is " + bean.price);
};
//parameter
var bean = {name: "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.
In this situation, the plugin will not detect the annotations on methods in your action.
To overcome this, set the "ignoreInterfaces" result parameter to false (true by default) to request that the plugin inspects all interfaces and superclasses of the action for annotations on the action's methods.
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="ignoreInterfaces">false</param>
</interceptor-ref>
<result type="json">
<param name="enableSMD">true</param>
<param name="ignoreInterfaces">false</param>
</result>
<interceptor-ref name="default"/>
</action>
|
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.
Version History
...
Version
...
Date
...
Author
...
Notes
...
0.20
...
Nov 02, 200t
...
musachy and Jeromy Evans
...
Add enableGZIP property. Support added for enum serialization. Thanks to Pavel Rodionov for the contribution
...
0.19
...
Nov 02, 200t
...
...
Return a JSON error instead of execeptions. Some bug fixes and refactoring. Thanks Joe Germuka and the other anonymous guys
...
0.18
...
Aug 10, 2007
...
...
Add SMDMethodsHack, fix 16,18,21 thanks for the patches guys!
...
0.17
...
Aug 10, 2007
...
Oleg Mikheev
...
Ignore properties matching 'excludedProperties' when generating SMD
...
0.16
...
Jul 27, 2007
...
...
Resolve issue where method is evaluated even if its result is ignored (#14)
...
0.15
...
Jul 18, 2007
...
...
Add excludedProperties attribute to interceptor
...
0.14
...
Jun 27, 2007
...
...
Add root (OGNL expression) attribute to interceptor
...
0.13
...
Jun 14, 2007
...
...