JSON support

The zero.core package includes support for JavaScript™ Object Notation (JSON) data structures.

JSON is a simple data format that is commonly used to transfer data to JavaScript client code. More information about JSON can be found at the JSON Web site json.org.

JSON APIs

zero.json.Json is the primary IBM® WebSphere® sMash API for working with JSON, including parsing, serializing, and converting representations.



The following example provides common APIs:

public class Json {
        
        /** Parse or decode a JSON encoded data from a String ... into a Java object*/
        public static Object decode(String str) throws IOException { ... }
        public static Object decode(InputStream is) throws IOException { ... }
        public static Object decode(Reader reader) throws IOException { ... }
        public static Object decode(URL url) throws IOException { ... }
        
        /** Decoding JSON objects into Maps where the order of the elements are to be preserved */
        public static Object decode(String str, boolean ordered) throws IOException { ... }
        public static Object decode(InputStream is, boolean ordered) throws IOException { ... }
        public static Object decode(Reader reader, boolean ordered) throws IOException { ... }
        public static Object decode(URL url, boolean ordered) throws IOException { ... }      
        
        /** Decoding JSON objects/arrays into custom Java Objects */
        public static Object decode(String str, JsonParserCallback cb) throws IOException { ... }
 	    public static Object decode(InputStream is, JsonParserCallback cb) throws IOException { ... }
        public static Object decode(Reader reader, JsonParserCallback cb) throws IOException { ... }
        public static Object decode(URL url, JsonParserCallback cb) throws IOException { ... }
              
        /** Serialize or encode a Java object as JSON data */
        public static String encode(Object obj) throws IllegalArgumentException, ...{ ... } 
        public static void encode(Object obj, OutputStream os) throws IllegalArgumentException, { ... }
        public static void encode(Object obj, Writer writer) throws IllegalArgumentException { ... }
        
        /** Encode a Java object with formatting */
        public static String encode(Object obj, boolean formatted) throws IllegalArgumentException, { ... }
        public static void encode(Object obj, OutputStream os, boolean formatted) throws IllegalArgumentException, { ... }
        public static void encode(Object obj, Writer writer, boolean formatted) throws IllegalArgumentException, { ... }
        
        /** Convert a decoded object into a custom Java object */
        public static <T> T toObject(Object jsonObj, Class<T> type) { ... }
        
}

decode() returns one of the following Java™ types:

  • java.lang.String
  • java.lang.Number
  • java.lang.Boolean
  • java.util.Map, which represents JSON objects
  • java.util.List, which represents JSON arrays
  • null

decode() can also be used to eliminate any deep copies. This is possible if the object being deserialized into is not one of the standard Java types in the previous example. The callback interface is used by the JSON parser to delegate creation and population of JSON objects and arrays to the callback implementation.

By default, conversions between Java objects and JSON operate only on public properties of the Java objects. The public properties of a Java object are properties associated with either public getter/setter methods or public (non-final) fields. Default conversions attempt to use getter/setter methods first. WebSphere sMash allows one to customize the conversion between Java objects and JSON. This is described in the Custom converters section below.

Java example for decoding JSON data

The following Java class illustrates how to parse JSON data that has been sent in the POST body in to a Java object.

import java.io.IOException;
import java.io.InputStream;

import zero.core.context.GlobalContext;
import zero.core.context.GlobalContextURIs.Request;
import zero.json.Json;

public class EventHandler {

	public void onPOST() throws IOException {
		// Get the input stream
		InputStream is = GlobalContext.zget(Request.input);
		Object json = Json.decode(is);
	}
}

Groovy example for decoding JSON data

The following Groovy script illustrates how to parse JSON data that has been sent in the POST body in to a Groovy object.

def json = zero.json.Json.decode(request.input[]);
// Do something useful with json
print json

PHP examples for working with JSON

The following example illustrates how to parse JSON data that has been sent in the POST body in to PHP variables.

<?php

// The raw post body is available in the variable $HTTP_RAW_POST_DATA
$employee = json_decode($HTTP_RAW_POST_DATA);

$sql = "SELECT * FROM employees WHERE employeeid = ".$employee['id'];

?>

The following example provides the same illustration, but using PHP streams.

<?php
$input = fopen("php://input", 'r);

$employee = json_decode(fread($input, 999));

$sql = "SELECT * FROM employees WHERE employeeid = ".$employee['id'];

?>

The following PHP script illustrates how to encode PHP arrays as JSON

<?php
	$indexedArray = array(
		"a", "b", "c"
		);
	$result = json_encode($indexedArray);
	echo "Indexed array = " . $result . "<br/>";
	$keyedArray = array(
		1 => "a", 
		2 => "b", 
		3 => "c"
	);
	$result = json_encode($keyedArray);
	echo "Keyed array = " . $result . "<br/>";
?>

This script would render the following output:

Indexed array = [ "a", "b", "c" ]
Keyed array = { "1": "a", "2": "b", "3": "c" }

Circular references

Avoid serializing Java objects that contain circular references: JSON is used to represent data, not objects. However, if circular references are detected, then the Json API replaces the references with JavaScript-like pointers with a $jref prefix. For example:

public class A {
        public B b;
}

public class B {
        public Foo[] foos;
}

public class Foo {
        public Bar bar;
}

public class Bar {
        public Foo y;
}

...
A a = new A();
a.b = new B();
a.b.foos = new Foo[1];

Foo foo = new Foo();
Bar x = new Bar();

foo.bar = x;
x.y = foo;

a.b.foos[0] = foo;

String result = Json.encode(a);
// result = {"b" : {"foos" : [{"bar" : {"y" : "$jref:this.b.foos[0]"}}]}

The process of converting JSON to Java restores circular references using the same convention. Thus, round-trip conversion is loss-less.

JSON Renderer

WebSphere sMash provides a library of renderers for common output patterns. The common case of serializing a Java object to the response as a JSON representation is codified as a renderer, as shown in the following Java example:

zput("/request/view", "JSON");
zput("/request/json/output", obj);
zero.core.views.ViewEngine.render();

The following example provides the same illustration, but using Groovy:

request.view="JSON"
request.json.output=obj
render()

The following example provides the same illustration, but using PHP:

<?php
	$customer = array('name' => 'John Smith');
	zput('/request/view', 'JSON');
	zput('/request/json/output', $customer);
	render_view();
?>

This example provides the following output:

{"name":"John Smith"}

Pretty print

By default, WebSphere sMash serializes JSON in a compact format. This is efficient for transmission, but not tailored for human consumption. You can force a pretty print serialization by adding the following line to the zero.config file for your application:

/config/json/prettyPrint=true

The following example shows this without pretty print:

{"name":"Bob"}

The following example shows this with pretty print:

{
   "name": "Bob"
}

JSON Parser Callbacks

To create a custom Java object from a JSON encoded string you could use the decode method to pass in a zero.json.JsonParserCallback callback object. You can use the boolean requireNamedObjects() method to specify that the JSON parser should provide the name of the objects or the arrays that are being created, or both.

The callback is invoked with JsonObjectCallback createJsonObject(String name) when the parser encounters a JSON object. The Parser provides the name of the object if requireNamedObjects() returns true. The callback is invoked with JsonArrayCallback createJsonArray(String name) when the JSON parser encounters a JSON array in the encoded stream.

The zero.json.JsonObjectCallback is invoked by the JSON parser in the course of processing an JSON object from a stream. The put(String key, Object value) allows the implementer of this interface to update its internal representation of the JSON object as the JSON object is parsed. The getObject() also provides the JSON parser with the representation of the JSON object to return.

The zero.json.JsonArrayCallback is invoked by the JSON parser in the course of processing an JSON object from a stream. The add(Object value) allows the implementer of this interface to update its internal representation of the JSON array as the JSON array is parsed. The getObject() also provides the JSON parser with the representation of the JSON array to return.

Custom converters

Custom converters are Java implementations of zero.json.converters.Converter:

public interface Converter extends Encoder {
        /**
        * Convert JSON representation to a Java object.
        */
        public Object toObject(Object json);
        
}
public interface Encoder {
        /**
        * The implementer of this interface serializes a custom Java object as JSON using the serializer provided. 
        * The custom encoder is declared using the global context. This interface provides a object reference map 
        * as a parameter which can be used find and add references to Java objects that might already be serialized. 
        * At the top of the encode method add a reference to the object being serialized prior to serializing it so that any circular
        * references to the object can be resolved. Any complex (array or object) attribute added to the serialized stream
        * should be updated in reference map with the object reference as a key and its JSON reference as its value.
		* You should also call JsonEncoder.push(refmap, yourObject) at the start and
		*  JsonEncoder.pop(refmap) before exiting the method. A Stack inside the refmap is internally used by the encoder
		*  to track circular dependencies.			
        *
        * In general the JSON reference to complex attributes are computed as follows. Any array reference to a complex
        * attribute concatenates the index of the attribute enclosed by "[" and "]" to the JSON reference of the containing
        * object. For instance jref + "[" + index + "]". Any object reference to a complex attribute appends
        * the name of the attribute separated by a period to the containing jref. For instance 
        *  jref + "." + attributeName .
        * 
        * @param object The object that needs to be serialized.
        * @param refMap a reference Map of objects that have been serialized to their JSON references. 
        * This map should updated with the reference of the object being serialized and any of its elements.
        * @param jref the JSON reference for the containing object. 
        * @param serializer the serializer object that needs to be used to create the custom json encoding for the object.
        */
        public void write(Object obj, Map<Object, String> refMap, String jref,  Serializer serializer) throws IllegalArgumentException, IntrospectionException, IllegalAccessException, InvocationTargetException, IOException ;
        
}

Custom converters are registered using the GlobalContext as class name/converter pairs, such as:

/config/json/converters += {
  "java.sql.Time" : "zero.json.converters.java.sql.TimeConverter",
  "java.sql.Timestamp" : "zero.json.converters.java.sql.TimestampConverter"
  "java.sql.Date" : "zero.json.converters.java.sql.DateConverter"
  "java.util.Date" : "zero.json.converters.java.util.DateConverter"
}

/config/json/derivedConverters += {
  "groovy.lang.Writable" : "zero.json.converters.groovy.lang.WritableConverter"
}

converters and derivedConverters differ in how they are matched. Converters registered under /config/json/converters are invoked when serializing from/parsing to an instance of the specified class; those under /config/json/derivedConverters are checked on superclasses and interfaces. converters are checked first.

JSONResultHandler for data access

The zero.data.php dependency provides a sample ResultHandler that is written in PHP. It is implemented in the JSONResultHandler.php file and automatically included in the PHP include path whenever you have zero.data.php as a dependency. This handler takes the result of a query operation, which is a JDBC ResultSet, and renders it as JSON. Specifically, this is an array of maps with column names as keys. See the Extending Data Access section for details.

Version 1.1.30763