Sunday, 26 October 2014

JSON serialization using GSON


This post gives basic idea of below points.

1. How to serialize map to json using Gson.
2. How to serialize null values.
3. How to implement custom serialization logic with build-in class.
4. How to implement custom serialization logic with custom class.
5. Inline custom serialization.
6. Common utility class for json related custom conversion.


Conversion  of Map into JSON String:

final Map<String, String> map = new HashMap<String, String>();
map.put("Name", "KK");
map.put("Age", "27");
map.put("Sex", "Male");
map.put("Address1", "India");
map.put("Address2", null);

final String jsonString = new Gson().toJson(map);
System.out.println("JSON Serialization default behaviour:" + jsonString);

Result:
{"Name":"KK","Age":"27","Address1":"India","Sex":"Male"}

By default Gson is ignoring null values with keys. In the above output "Address2" is missing. We can serialize that using serializeNulls() method.

Below is the complete example.
final Map<String, String> map = new HashMap<String, String>();
map.put("Name", "KK");
map.put("Age", "27");
map.put("Sex", "Male");
map.put("Address1", "India");
map.put("Address2", null);
final String jsonString = new Gson().toJson(map);
final String jsonStringWithNull = new GsonBuilder().serializeNulls().create().toJson(map);
System.out.println("JSON Serialization default behaviour:" + jsonString);
System.out.println("JSON Serialization with null values:" + jsonStringWithNull);
Result:
JSON Serialization default behaviour:{"Name":"KK","Age":"27","Address1":"India","Sex":"Male"}
JSON Serialization with null values:{"Name":"KK","Age":"27","Address2":null,"Address1":"India","Sex":"Male"}

While deserialise Map<String,Object> throws ClassCastException


public class StudentFormBean {

    private String registerNumber;
    private String name;
    private int age;

   /*getter/setters and constructors removed from here. get it from attachment*/
  
}

Serialization of Map<String,StudentFormBean> gives correct json output but the reverse gives ClassCastException.

Conversion Map of objects to json

final Map<String, StudentFormBean> map = new HashMap<String, StudentFormBean>();
map.put("Student1",    new StudentFormBean("ECE2014001","Jon",24));
map.put("Student2",    new StudentFormBean("ECE2014002","James",23));
map.put("Student3",    new StudentFormBean("ECE2014003","Hari",24));
map.put("Student4",    new StudentFormBean("ECE2014004","Sam",23));

final String json = new Gson().toJson(map); 
System.out.println(json);

Output

{"Student4":{"registerNumber":"ECE2014004","name":"Sam","age":23},"Student3":{"registerNumber":"ECE2014003","name":"Hari","age":24},"Student2":{"registerNumber":"ECE2014002","name":"James","age":23},"Student1":{"registerNumber":"ECE2014001","name":"Jon","age":24}}

JSON string to Map of object (Deserialization)
When we try to convert the JSON string to map of object as below then it throws ClassCastException.

Map<String,StudentFormBean> convertedMap = new Gson().fromJson(json, map.getClass());
for(Entry<String,StudentFormBean> studentEntry : convertedMap.entrySet()){
      StudentFormBean bean = studentEntry.getValue();
      System.out.println(bean);
}

Output

Exception in thread "main" java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to org.jsonconversion.model.StudentFormBean
    at org.jsonconversion.JsonConversion.main(JsonConversion.java:29)


In the above map.getClass() gives the reverse conversion type, but it failes to convert to the particular object. Instead of it got converted to com.google.gson.internal.LinkedTreeMap.

for(Entry<String,StudentFormBean> studentEntry : convertedMap.entrySet()){
    System.out.println(studentEntry.getValue());
}

Output

{registerNumber=ECE2014004, name=Sam, age=23.0}
{registerNumber=ECE2014003, name=Hari, age=24.0}
{registerNumber=ECE2014002, name=James, age=23.0}
{registerNumber=ECE2014001, name=Jon, age=24.0}

In-order to deserialize json string to map of objects, do as below.
Type listType = new TypeToken<HashMap<String,StudentFormBean>>(){}.getType();

Code 
Map<String,StudentFormBean> convertedMap = new Gson().fromJson(json, listType);
for(Entry<String,StudentFormBean> studentEntry : convertedMap.entrySet()){
      StudentFormBean bean = studentEntry.getValue();
      System.out.println(bean);
} 

Custom Json Conversion(serialization of JavaObject to JSON String):
We can write custom serialization logic by implementing JsonSerializer interface.

Below is the sample implementation to convert Integer object to String while serialization.

public class JsonIntegerStringConversion implements JsonSerializer {

 public JsonElement serialize(Integer integer, Type type, JsonSerializationContext context) {
  return new JsonPrimitive(String.valueOf(integer));
 } 

}

Below is the code to use the custom serialization class while converting javaObject to json string.
final Map map = new HashMap();
map.put("Name", "KK");
map.put("Age", new Integer(27));
map.put("Sex", "Male");
map.put("Address1", "India");
map.put("Address2", null);
final String jsonString = new GsonBuilder().serializeNulls()
  .registerTypeAdapter(Integer.class, new JsonIntegerStringConversion()).create()
  .toJson(map);

Result
{"Name":"KK","Age":"27","Address2":null,"Address1":"India","Sex":"Male"}

without custom logic the Integer object will be converted to int value(with out quotes in json string). Look at the age attribute.
System.out.println(new Gson().toJson(map));

{"Name":"KK","Age":27,"Address1":"India","Sex":"Male"}

Conversion of custom objects

Consider the below bean class and I need to serialize some of the properties using custom serialization class.

public class Address implements Serializable {

 private static final long serialVersionUID = -291393950554966155L;
 private String address1;
 private String street;
 private String state;
 private String pincode;
/** Setter getters removed from here*/
}

public class StudentFormBean implements Serializable {

 private static final long serialVersionUID = -4256927871832916527L;
 private String registerNumber;
 private String name;
 private int age;
/** Setter getters removed from here*/
}

public class FormBean implements Serializable {

 private static final long serialVersionUID = -5600189373922519204L;
 private StudentFormBean studentFormBean;
 private Address address;
/** Setter getters removed from here*/
}

Custom serialization implementation

return new JsonSerializer() {
 public JsonElement serialize(FormBean bean, Type arg1, JsonSerializationContext arg2) {
  JsonObject object = new JsonObject();
  object.addProperty("registerNumber", bean.getStudentFormBean().getRegisterNumber());
  object.addProperty("Name", bean.getStudentFormBean().getName());
  object.addProperty("address", bean.getAddress().getAddress1());
  return object;
 }
};

In the above custom conversion will serialize registerNumber, Name, and address properties.

Result
{"registerNumber":"100","Name":"KK","address":"India"}

We have a JsonIntegerStringConversion class which has custom conversion logic when Integer object is serialized. We can avoid by creating individual class for each type(Integer,Boolean, etc) by writing one utility class.
Complete Source code
/**
 * Utility class for custom json serialization.
 * 
 * @author krishnakumar
 * 
 */
public class JsonConversionUtil {

 /**
 * Used to convert FormBean object to custom defined JSON object.
 * 
 * @return
 */
public static JsonSerializer formBeanCustomSerializer() {
 return new JsonSerializer() {
  public JsonElement serialize(FormBean bean, Type arg1, JsonSerializationContext arg2) {
   JsonObject object = new JsonObject();
   object.addProperty("registerNumber", bean.getStudentFormBean().getRegisterNumber());
   object.addProperty("Name", bean.getStudentFormBean().getName());
   object.addProperty("address", bean.getAddress().getAddress1());
   return object;
  }
 };
}

/**
 * Convert integer object to String.
 * 
 * @return
 */
public static JsonSerializer integerToStringSerializer() {
 return new JsonSerializer() {
  public JsonElement serialize(Integer integer, Type type,
    JsonSerializationContext context) {
   System.out.println("Custom Serialization Called : " + integer);
    return new JsonPrimitive(String.valueOf(integer));
   }
  };
 }
}


/**
 * convert Integer object to String while serialization.
 * 
 * @author krishnakumar
 * 
 */
public class JsonIntegerStringConversion implements JsonSerializer {

 public JsonElement serialize(Integer integer, Type type, JsonSerializationContext context) {
  return new JsonPrimitive(String.valueOf(integer));
 }

}

/**
 * Custom JSON converter.
 * 
 * @author krishnakumar
 * 
 */
public class JSonCustomConversion {
 public static void main(String[] args) {

  final StudentFormBean studentBean = new StudentFormBean("100", "KK", 27);
 final Address addressBean = new Address("India", "27-New Street", "TamilNadu", "638056");
 final FormBean bean = new FormBean(studentBean, addressBean);
 // Serialization of custom java object using default serialization.
 System.out.println("Default Serialization : "
   + new GsonBuilder().serializeNulls().create().toJson(bean));

 // Serialization of custom java object using default custom
 // serialization logic.
 System.out.println("Serialization of custom java object with custom logic :"
   + new GsonBuilder()
     .serializeNulls()
     .registerTypeAdapter(FormBean.class,
       JsonConversionUtil.formBeanCustomSerializer()).create()
     .toJson(bean));

 final Map map = new HashMap();
 map.put("Name", "KK");
 map.put("Age", new Integer(27));
 map.put("Sex", "Male");
 map.put("Address1", "India");
 map.put("Address2", null);

 // Default conversion
 System.out.println(new Gson().toJson(map));

 // Custom conversion logic implemented in separate class.
 final String jsonString = new GsonBuilder().serializeNulls()
   .registerTypeAdapter(Integer.class, new JsonIntegerStringConversion()).create()
   .toJson(map);
 System.out.println("Integer to String conversion using separate class : " + jsonString);

 // Utility class related to JSON conversion.
 System.out.println("Integer to String conversion using common utility class :"
   + new GsonBuilder()
     .registerTypeAdapter(Integer.class,
       JsonConversionUtil.integerToStringSerializer()).create()
     .toJson(map));

 // Inline implementation.
 System.out.println("Custom serialization logic using inline implementation :"
   + new GsonBuilder()
     .registerTypeAdapter(Integer.class, new JsonSerializer() {
      public JsonElement serialize(Integer integer, Type type,
        JsonSerializationContext context) {
       System.out.println("Custom Serialization Called : " + integer);
        return new JsonPrimitive(String.valueOf(integer));
       }
      }).create().toJson(map));

 }
}


Default Serialization : {"studentFormBean":{"registerNumber":"100","name":"KK","age":27},"address":{"address1":"India","street":"27-New Street","state":"TamilNadu","pincode":"638056"}}
Serialization of custom java object with custom logic :{"registerNumber":"100","Name":"KK","address":"India"}
{"Name":"KK","Age":27,"Address1":"India","Sex":"Male"}
Integer to String conversion using separate class : {"Name":"KK","Age":"27","Address2":null,"Address1":"India","Sex":"Male"}
Custom Serialization Called : 27
Integer to String conversion using common utility class :{"Name":"KK","Age":"27","Address1":"India","Sex":"Male"}
Custom Serialization Called : 27
Custom serialization logic using inline implementation :{"Name":"KK","Age":"27","Address1":"India","Sex":"Male"}


Complete source code can be downloaded from 

https://github.com/krishnakumarsamy/JSONConversion.git

Saturday, 17 May 2014

Spring HATEOAS



We can download a sample project in the below mentioned URL.
https://github.com/krishnakumarsamy/Spring.git

This project is used to give basic idea of

1. How to write Spring REST web service.
2. How to create custom message converters in spring
3. How to use spring HATEOAS in REST.
4. JSON/XML conversion of custom objects.
5. JSON/XML conversion of List
6. How to avoid default values while serialization in (XML/JSON).
7. How to use @ControllerAdvice to handle all the exceptions from all the controllers.
8. How to use @ExceptionHandler to handle exception in controller.
9. How to handle method not supported exception using (@ExceptionHandler(HttpRequestMethodNotSupportedException.class)).
10. How to handle custom defined exception from all the controller using (@ControllerAdvice)
11. How to use @PathVariable in spring REST


Download the sample from the above location.
Steps to execute: 
1. Go inside hypermedia folder.
2. Issue the command mvn package
3. the war will be created in hypermedia/target folder. Copy it into tomcat.../.../webapps/ folder and start tomcat.

@RequestMapping(value = "/all", produces = { "text/plain", "text/html",
  "application/xml", "application/json" })
public ResponseEntity<StudentDAO> getStudentDetails() {
 return new ResponseEntity<StudentDAO>(studentdao, HttpStatus.OK);
}

In the above code conversion of StudentDAO will be done automatically by adding jackson jars into class path.

How to create custom message converters in spring:

There is a custom converter has been added to convert the StudentDAO object into custom formats like (text/html and text/plain).

Below is the code

public class StudentMessageConverter extends AbstractHttpMessageConverter<StudentDAO> {
 public StudentMessageConverter() {
  super(new MediaType("text", "plain", Charset.forName("UTF-8")), new MediaType("text",
    "html", Charset.forName("UTF-8")));
 }

 @Override
 protected boolean supports(Class<?> clazz) {
  return clazz.getSimpleName().equalsIgnoreCase("studentdao");
 }

 @Override
 protected StudentDAO readInternal(Class<? extends StudentDAO> clazz,
   HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
  return null;
 }

 @Override
 protected void writeInternal(StudentDAO studentDao, HttpOutputMessage outputMessage)
   throws IOException, HttpMessageNotWritableException {
  StringBuffer studentRecords = new StringBuffer();
  int count = 1;
  for (Student student : studentDao.getStudentList()) {
   studentRecords.append("\n\nRecord").append(count);
   studentRecords.append("\nId").append(" = ").append(student.getStudentId())
     .append("\nName").append(" = ").append(student.getStudentName()).append("\n")
     .append("Age").append(" = ").append(student.getAge());
   count++;
  }
  outputMessage.getBody().write(studentRecords.toString().getBytes());

 }

}
For complete code refer the sample(StudentMessageConverter.java).


How to use spring HATEOAS in REST:

@RequestMapping(value = "/")
public ResponseEntity<Student> getLinks() throws HypermediaException {
 student = new Student();
 for (Student student1 : studentdao.getStudentList()) {
  student.add(linkTo(
    methodOn(StudentController.class).getStudent(student1.getStudentId())).withRel(
    "Get_Student" + student1.getStudentId()));
 }
 student.add(linkTo(methodOn(StudentController.class).getStudentDetails()).withRel(
   "Get_All_Students"));
 return new ResponseEntity<Student>(student, HttpStatus.OK);
}
We have to extend ResourceSupport class of spring HATEOAS and this will give flexibility of adding links in the response.

Refer - Student.java


How to avoid default values while serialization in (XML/JSON):

The  below annotation will help to avoid default values while serialization.

@JsonInclude(Include.NON_EMPTY)

Refer - Student.java


How to use @ControllerAdvice to handle all the exceptions from all the controllers:
@ControllerAdvice annotation is used to handle global exceptions. If any of the exception we need to handle commonly for all the controllers then we can use this annotation.

Sample :
@ControllerAdvice
public class CommonControllerExceptionHandler {...}
Refer - CommonControllerExceptionHandler.java

Inside the class use @ExceptionHandler annotation to handle individual exceptions.

Ex : @ExceptionHandler(HypermediaException.class)
In the above annotation  HypermediaException is a custom exception class which extends Exception class.

@PathVariable:

This annotation is used to map values which comes from request URL.

@RequestMapping(value = "/get/{id}", produces = { "application/xml",
  "application/json" }, method = { RequestMethod.GET }) 
If we need to get the student 1 details then we can form URL as

/student/1

For more details refer - StudentController.java


How to verify the result:
http://localhost:8080/hypermedia/
The above URL is the base URL. If we hit the base URL then we will get list of URLS in the response.

Client need not depend on server side changes. If we decided the rel value then in server side we can make changes independently. If the URL is hard coded then for every server side changes we need to implement the same in client also. So HATEOAS will avoid the dependency.

We can use the REST client which is the add-on for firefox.
or in Unix / MAC/ Linux use curl command

Request:
http://localhost:8080/hypermedia/
Response:
<student>
<atom:link rel="Get_Student1" href= "http://localhost:8080/hypermedia/student/1" />
<atom:link rel="Get_Student2" href= "http://localhost:8080/hypermedia/student/2" />
<atom:link rel="Get_Student3" href= "http://localhost:8080/hypermedia/student/3" />
<atom:link rel="Get_Student4" href= "http://localhost:8080/hypermedia/student/4" />
<atom:link rel="Get_All_Students" href= "http://localhost:8080/hypermedia/student/all" />
</student>
we can use any one of the above URL to get the details.

Ex Get_Student1 Link:

Request :
http://localhost:8080/hypermedia/student/1

Response XML: 

<student>
<age>21</age>
<regNo>2</regNo>
<name>Ram</name>
</student>

Request using curl command:
 curl -H Accept:application/json http://localhost:8080/hypermedia/student/1 
Response JSON: 
{"regNo":"2","name":"Ram","age":"21"}