| --- |
| layout: plugin |
| title: JSON plugin |
| parent: |
| url: /plugins |
| title: Plugins |
| --- |
| |
| # JSON Plugin |
| {:.no_toc} |
| |
| * Will be replaced with the ToC, excluding a header |
| {:toc} |
| |
| ## Description |
| |
| The JSON plugin provides a `json` result type that serializes actions into JSON |
| |
| 1. The `content-type` must be `application/json` |
| 2. The JSON content must be well formed, see [json.org](http://www.json.org) for grammar. |
| 3. Action must have a public "setter" method for fields that must be populated. |
| 4. 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. |
| 5. 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: |
| |
| ```json |
| { |
| "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. |
| |
| > So serialize your objects to JSON in javascript see [json2](http://json.org/json2.js). |
| |
| `root` attribute must be set on the `JSONInterceptor` when dealing with JSON array. |
| |
| This plugin also provides [JSON Ajax Validation](json-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: |
| |
| ```xml |
| <dependencies> |
| ... |
| <dependency> |
| <groupId>org.apache.struts</groupId> |
| <artifactId>struts2-json-plugin</artifactId> |
| <version>STRUTS_VERSION</version> |
| </dependency> |
| ... |
| </dependencies> |
| ``` |
| |
| ## 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| |
| |
| ### Excluding properties |
| |
| 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: |
| |
| ```xml |
| <!-- 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> |
| ``` |
| |
| ### Including properties |
| |
| A comma-delimited list of regular expressions can be passed to the JSON Result to restrict which properties will |
| be serialized. ONLY properties matching any of these regular expressions will be included in the serialized output. |
| |
| > 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 property expression matches a property first. |
| |
| ```xml |
| <!-- Result fragment --> |
| <result type="json"> |
| <param name="includeProperties"> |
| ^entries\[\d+\].clientNumber, |
| ^entries\[\d+\].scheduleNumber, |
| ^entries\[\d+\].createUserId |
| </param> |
| </result> |
| ``` |
| |
| ### Root Object |
| |
| Use the `root` attribute (OGNL expression) to specify the root object to be serialized. |
| |
| ```xml |
| <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**. |
| |
| ```xml |
| <interceptor-ref name="json"> |
| <param name="root">bean1.bean2</param> |
| </interceptor-ref> |
| ``` |
| |
| ### Wrapping |
| |
| 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: |
| |
| ```xml |
| <result type="json"> |
| <param name="wrapPrefix">/*</param> |
| <param name="wrapSuffix">*/</param> |
| </result> |
| ``` |
| |
| - Add a prefix: |
| |
| ```xml |
| <result type="json"> |
| <param name="wrapPrefix">{}&&</param> |
| </result> |
| ``` |
| |
| - Wrap for file upload: |
| |
| ```xml |
| <result type="json"> |
| <param name="wrapPrefix"><![CDATA[<html><body><textarea>]]></param> |
| <param name="wrapSuffix"><![CDATA[</textarea></body></html>]]></param> |
| </result> |
| ``` |
| |
| ### Wrap with Comments |
| |
| `wrapWithComments` is deprecated from 0.34, use `wrapPrefix` and `wrapSuffix` instead. |
| |
| > `wrapWithComments` can turn safe JSON text into dangerous text. For example, |
| > `"\*/ alert('XSS'); /\*"` |
| > Thanks to Douglas Crockford for the tip! Consider using **prefix** instead. |
| |
| If the serialized JSON is `{name: 'El Zorro'}`. Then the output will be: `{}&& ({name: 'El Zorro'})`. |
| |
| If the `wrapWithComments` (false by default) attribute is set to true, the generated JSON is wrapped with comments like: |
| |
| ```json |
| /* { |
| "doubleVal": 10.10, |
| "nestedBean": { |
| "name": "Mr Bean" |
| }, |
| "list": ["A", 10, 20.20, { |
| "firstName": "El Zorro" |
| }], |
| "array": [10, 20] |
| } */ |
| ``` |
| To strip those comments use: |
| |
| ```javascript |
| var responseObject = eval("("+data.substring(data.indexOf("\/\*")+2, data.lastIndexOf("\*\/"))+")"); |
| ``` |
| |
| ### Prefix |
| |
| `prefix` is deprecated from 0.34, use `wrapPrefix` and `wrapSuffix` instead. |
| |
| If the parameter `prefix` is set to true, the generated JSON will be prefixed with `{}&& `. This will help prevent |
| hijacking. See [this Dojo Ticket](http://trac.dojotoolkit.org/ticket/6380) for details: |
| |
| ```xml |
| <result type="json"> |
| <param name="prefix">true</param> |
| </result> |
| ``` |
| |
| ### 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: |
| |
| ```xml |
| <result type="json"> |
| <param name="ignoreHierarchy">false</param> |
| </result> |
| ``` |
| |
| ### Enumerations |
| |
| By default, an Enum is serialized as a `name=value` pair where `value = name()`. |
| |
| ```java |
| 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. |
| |
| ```java |
| 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`: |
| |
| ```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. |
| |
| ```xml |
| <result type="json"> |
| <param name="enableGZIP">true</param> |
| </result> |
| ``` |
| |
| ### Preventing the browser from caching the response |
| |
| Set `noCache` to true (false by default) to set the following headers in the response: |
| |
| - `Cache-Control: no-cache` |
| - `Expires: 0` |
| - `Pragma: No-cache` |
| |
| ```xml |
| <result type="json"> |
| <param name="noCache">true</param> |
| </result> |
| ``` |
| |
| ### Excluding properties with null values |
| |
| By default fields with null values are serialized like `{property_name: null}`. This can be prevented |
| by setting `excludeNullProperties` to true. |
| |
| ```xml |
| <result type="json"> |
| <param name="excludeNullProperties">true</param> |
| </result> |
| ``` |
| |
| ### Status and Error code |
| |
| Use `statusCode` to set the status of the response: |
| |
| ```xml |
| <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): |
| |
| ```json |
| <result type="json"> |
| <param name="errorCode">404</param> |
| </result> |
| ``` |
| |
| ### JSONP |
| |
| 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": |
| |
| ```xml |
| <result type="json"> |
| <param name="callbackParameter">callback</param> |
| </result> |
| ``` |
| |
| And that the serialized JSON is `{name: 'El Zorro'}`. Then the output will be: `exec({name: 'El Zorro'})`. |
| |
| ### Content Type |
| |
| 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. |
| |
| ```xml |
| <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: |
| |
| ```xml |
| <result type="json"> |
| <param name="encoding">UTF-8</param> |
| </result> |
| ``` |
| |
| ### Customizing the output |
| |
| Since Struts 2.5.14, you can implement the `org.apache.struts2.json.JSONWriter` interface to customize the generated json response. |
| The implementation should then be defined in `struts.xml` like: |
| |
| ```xml |
| <struts> |
| <bean type="org.apache.struts2.json.JSONWriter" name="myJSONWriter" class="com.mycompany.MyJSONWriter" |
| scope="prototype"/> |
| <constant name="struts.json.writer" value="myJSONWriter"/> |
| </struts> |
| ``` |
| |
| There is an example at [struts-examples/json-customize/FlexJSONWriter.java](https://gitbox.apache.org/repos/asf?p=struts-examples.git;a=blob_plain;f=json-customize/src/main/java/org/demo/FlexJSONWriter.java;hb=HEAD). |
| It replaces Struts default json serializer with [Flexjson](http://flexjson.sourceforge.net/) as below: |
| |
| ```java |
| import flexjson.JSONSerializer; |
| import flexjson.transformer.DateTransformer; |
| import org.apache.struts2.json.JSONException; |
| import org.apache.struts2.json.JSONWriter; |
| |
| public class FlexJSONWriter implements JSONWriter { |
| private String dateFormatter; |
| |
| public String write(Object object) throws JSONException { |
| return this.write(object, null, null, false); |
| } |
| |
| public String write(Object object, Collection<Pattern> excludeProperties, Collection<Pattern> includeProperties, |
| boolean excludeNullProperties) throws JSONException { |
| |
| JSONSerializer serializer = new JSONSerializer(); |
| if (excludeProperties != null) { |
| for (Pattern p : excludeProperties) { |
| serializer = serializer.exclude(p.pattern()); |
| } |
| } |
| if (includeProperties != null) { |
| for (Pattern p : includeProperties) { |
| serializer = serializer.include(p.pattern()); |
| } |
| } |
| if (excludeNullProperties) { |
| serializer = serializer.transform(new ExcludeTransformer(), void.class); |
| } |
| if (dateFormatter != null) { |
| serializer = serializer.transform(new DateTransformer(dateFormatter), Date.class); |
| } |
| return serializer.serialize(object); |
| } |
| //... |
| ``` |
| |
| > Flexjson is a lightweight library for serializing and deserializing Java objects into and from JSON. |
| |
| ## Example |
| |
| ### Setup Action |
| |
| This simple action has some fields: |
| |
| Example: |
| |
| ```java |
| 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 |
| |
| 1. Add the map inside a package that extends `json-default` |
| 2. Add a result of type `json` |
| |
| Example with Convention Plugin Configuration: |
| |
| ```java |
| 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: |
| |
| ```xml |
| <?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 |
| |
| ```json |
| { |
| "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: |
| |
| ```jaba |
| @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: |
| |
| ```xml |
| <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 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](http://manual.dojotoolkit.org/WikiHome/DojoDotBook/Book9) |
| to advertise the remote service. Let's work it out with an example(useless as most examples). |
| |
| First write the action: |
| |
| ```java |
| 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: |
| |
| ```java |
| 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: |
| |
| ```xml |
| <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> |
| ``` |
| |
| 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: |
| |
| ```jsp |
| <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 |
| |
| 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. |
| |
| > 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. |
| |
| ```xml |
| <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> |
| ``` |