I have a Struts 2 application in which i use the struts 2 json plugin for json handling.
Now i want to use the dojo data grid to populate data from an action. I can call the action. All the inbuilt data types are working in the action. However when i use a custom object in my class, i get errors in the action class.
I want to use ItemFileReadStore as a store for the grid which needs data in the format like:
items: [{obj1},{obj2},{obj3},{obj4}]
Now i have a class called Device. I want to send a list of Device objects back to the client. But how do i provide data in the above format and use it on client side.?
Edit:
I get the following error:
E com.ibm.ws.webcontainer.webapp.WebApp logError SRVE0293E: [Servlet Error]-[com.googlecode.jsonplugin.JSONException: com.googlecode.jsonplugin.JSONException: java.lang.reflect.InvocationTargetException]: com.ibm.ws.webcontainer.webapp.WebAppErrorReport: com.googlecode.jsonplugin.JSONException: com.googlecode.jsonplugin.JSONException: java.lang.reflect.InvocationTargetException
at com.ibm.ws.webcontainer.webapp.WebAppDispatcherContext.sendError(WebAppDispatcherContext.java:624)
at com.ibm.ws.webcontainer.srt.SRTServletResponse.sendError(SRTServletResponse.java:1071)
at org.apache.struts2.dispatcher.Dispatcher.sendError(Dispatcher.java:725)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:485)
at org.apache.struts2.dispatcher.FilterDispatcher.doFilter(FilterDispatcher.java:395)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:188)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:116)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain._doFilter(WebAppFilterChain.java:77)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:852)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:917)
at com.ibm.ws.webcontainer.extension.DefaultExtensionProcessor.invokeFilters(DefaultExtensionProcessor.java:924)
at com.ibm.ws.webcontainer.extension.DefaultExtensionProcessor.handleRequest(DefaultExtensionProcessor.java:852)
at com.ibm.ws.webcontainer.webapp.WebApp.handleRequest(WebApp.java:3610)
at com.ibm.ws.webcontainer.webapp.WebGroup.handleRequest(WebGroup.java:274)
at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:926)
at com.ibm.ws.webcontainer.WSWebContainer.handleRequest(WSWebContainer.java:1557)
at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:173)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:455)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewInformation(HttpInboundLink.java:384)
at com.ibm.ws.http.channel.inbound.impl.HttpICLReadCallback.complete(HttpICLReadCallback.java:83)
at com.ibm.ws.tcp.channel.impl.AioReadCompletionListener.futureCompleted(AioReadCompletionListener.java:165)
at com.ibm.io.async.AbstractAsyncFuture.invokeCallback(AbstractAsyncFuture.java:217)
at com.ibm.io.async.AsyncChannelFuture.fireCompletionActions(AsyncChannelFuture.java:161)
at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:138)
at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:202)
at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:766)
at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:896)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1527)
What is the reason for this error. My action class is:
jsonWrapper.setIdentifier("firstName");
jsonWrapper.getListItems().add(User.getUser("t2590pk"));
jsonWrapper.getListItems().add(User.getUser("t8923sm"));
jsonWrapper.setItems(jsonWrapper.gson.toJson(jsonWrapper.getListItems()));
System.out.println(jsonWrapper.getItems());
Struts config:
<action name="jsonTest" class="com.dcx.ispeed.actions.JSONTest">
<result type="json">
<param name="excludeProperties">
gson
</param>
</result>
</action>
The jsonWrapper class:
/**
*
*/
package com.dcx.ispeed.business;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
import com.ibm.ws.http.HttpRequest;
/**
* #author t2590pk
*
*/
public class JSONWrapper {
public Gson gson = new Gson();
private String identifier;
private String label;
private String items;
private List listItems = new ArrayList();
public String getIdentifier() {
return identifier;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getItems() {
return items;
}
public void setItems(String items) {
this.items = items;
}
public List getListItems() {
return listItems;
}
public void setListItems(List listItems) {
this.listItems = listItems;
}
/**
*
*/
public JSONWrapper() {
System.out.println("Calling JSON wrapper constructor.");
}
}
Thanks.. :)
You can use google Gson package as following
import com.google.gson.Gson;
String json = "{\"name\":\"ABC\",\"address\":\"some address\"}";
Gson gson = new Gson();
Person person = gson.fromJson(json, Person.class);
public class Person{
public String name;
public String address;
}
Note: you have to implement default constructor and all getter and setter for the Person class.
The array in your case will be a Set
this might be happen with your gson library so please update your gson library and etends jsonException when u use json that will show where in your code exception is coming.
Related
I am trying to serialise and deserialise a class RuleMessage but can't get it to work. Here is my code:
public class RuleMessage {
private String id;
private SerializableRunnable sRunnable;
public RuleMessage(String id, SerializableRunnable sRunnable) {
this.id = id;
this.sRunnable = sRunnable;
}
}
public interface SerializableRunnable extends Runnable, Serializable {
}
#Test
public void testSerialization() throws JsonProcessingException {
MAPPER.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
SerializableRunnable r = () -> System.out.println("Serializable!");
RuleMessage rule = new RuleMessage("1", r);
System.out.println(MAPPER.writeValueAsString(businessRule));
}
I am using Java 8. Can someone tell me if this is possible in the Jackson library?
Jackson was created to keep object state not behaviour. This is why it tries to serialise POJO's properties using getters, setters, etc. Serialising lambdas break this idea. Theres is no any property to serialise, only a method which should be invoked. Serialising raw lambda object is really bad idea and you should redesign your app to avoid uses cases like this.
In your case SerializableRunnable interface extends java.io.Serializable which gives one option - Java Serialisation. Using java.io.ObjectOutputStream we can serialise lambda object to byte array and serialise it in JSON payload using Base64 encoding. Jackson supports this scenario providing writeBinary and getBinaryValue methods.
Simple example could look like below:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class JsonLambdaApp {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SerializableRunnable action = () -> System.out.println("Serializable!");
String json = mapper.writeValueAsString(new RuleMessage("1", action));
System.out.println(json);
RuleMessage ruleMessage = mapper.readValue(json, RuleMessage.class);
ruleMessage.getsRunnable().run();
}
}
#JsonSerialize(using = LambdaJsonSerializer.class)
#JsonDeserialize(using = LambdaJsonDeserializer.class)
interface SerializableRunnable extends Runnable, Serializable {
}
class LambdaJsonSerializer extends JsonSerializer<SerializableRunnable> {
#Override
public void serialize(SerializableRunnable value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream)) {
outputStream.writeObject(value);
gen.writeBinary(byteArrayOutputStream.toByteArray());
}
}
}
class LambdaJsonDeserializer extends JsonDeserializer<SerializableRunnable> {
#Override
public SerializableRunnable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
byte[] value = p.getBinaryValue();
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value);
ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream)) {
return (SerializableRunnable) inputStream.readObject();
} catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
}
class RuleMessage {
private String id;
private SerializableRunnable sRunnable;
#JsonCreator
public RuleMessage(#JsonProperty("id") String id, #JsonProperty("sRunnable") SerializableRunnable sRunnable) {
this.id = id;
this.sRunnable = sRunnable;
}
public String getId() {
return id;
}
public SerializableRunnable getsRunnable() {
return sRunnable;
}
}
Above code prints JSON:
{
"id" : "1",
"sRunnable" : "rO0ABXNyACFqYXZhLmxhbmcuaW52b2tlLlNlcmlhbGl6ZWRMYW1iZGFvYdCULCk2hQIACkkADmltcGxNZXRob2RLaW5kWwAMY2FwdHVyZWRBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wADmNhcHR1cmluZ0NsYXNzdAARTGphdmEvbGFuZy9DbGFzcztMABhmdW5jdGlvbmFsSW50ZXJmYWNlQ2xhc3N0ABJMamF2YS9sYW5nL1N0cmluZztMAB1mdW5jdGlvbmFsSW50ZXJmYWNlTWV0aG9kTmFtZXEAfgADTAAiZnVuY3Rpb25hbEludGVyZmFjZU1ldGhvZFNpZ25hdHVyZXEAfgADTAAJaW1wbENsYXNzcQB+AANMAA5pbXBsTWV0aG9kTmFtZXEAfgADTAATaW1wbE1ldGhvZFNpZ25hdHVyZXEAfgADTAAWaW5zdGFudGlhdGVkTWV0aG9kVHlwZXEAfgADeHAAAAAGdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAHZyABxjb20uY2Vsb3hpdHkuSnNvblR5cGVJbmZvQXBwAAAAAAAAAAAAAAB4cHQAIWNvbS9jZWxveGl0eS9TZXJpYWxpemFibGVSdW5uYWJsZXQAA3J1bnQAAygpVnQAHGNvbS9jZWxveGl0eS9Kc29uVHlwZUluZm9BcHB0ABZsYW1iZGEkbWFpbiQ1YzRiNmEwOCQxcQB+AAtxAH4ACw=="
}
and lambda:
Serializable!
See also:
How to serialize a lambda?
How to serialize a lambda function in Java?
First, in RuleMessage you have to either create getters / setters or make the fields public in order to provide Jackson access to the fields.
Your code then prints something like this:
{"#class":"RuleMessage","id":"1","sRunnable":{"#class":"RuleMessage$$Lambda$20/0x0000000800b91c40"}}
This JSON document cannot be deserialized because RuleMessage has no default constructor and the lambda cannot be constructed.
Instead of the lambda, you could create a class:
public class Runner implements SerializableRunnable {
#Override
public void run() {
System.out.println("Serializable!");
}
}
and construct your pojo like this:
new RuleMessage("1", new Runner())
The Jackson deserializer is now able to reconstruct the objects and execute the runner.
I am working on an API that produces both XML and JSON responses. I have one element in the response which requires an attribute only in XML response. Also, when the value is null, the element shouldn't show up in the response for both formats.
Expectation:
XML:
<name>john</name>
<status type="text">married</status>
JSON:
"name":"john"
"status":"married"
This is my code:
/**
* POJO with bunch of LOMBOK annotations to avoid boiler-plate code.
*/
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
#Data
public class User implements Customer, Serializable {
private static final long serialVersionUID = 1L;
private Status status;
private String name;
/**
* Matrital status of the user.
*/
#Builder
#Value
public static class Status {
#JacksonXmlText
private String maritalStatus;
#JacksonXmlProperty(isAttribute = true)
private String type = "text";
}
}
With the above change, I am getting the correct XML response but JSON response also returns type=text
"status" : {
"maritalStatus" : "married",
"type" : "text"
}
I tried to add #JsonValue to private String maritalStatus, that solved the JSON response but it broke XML response by not adding the attribute to the element.
Can someone please help?
Probably the easiest way is to implement custom serialiser for User.Status and produce different output for different kinds of representation.
class UserStatusJsonSerializer extends JsonSerializer<User.Status> {
#Override
public void serialize(User.Status value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (gen instanceof ToXmlGenerator) {
ToXmlGenerator toXmlGenerator = (ToXmlGenerator) gen;
serializeXml(value, toXmlGenerator);
} else {
gen.writeString(value.getMaritalStatus());
}
}
private void serializeXml(User.Status value, ToXmlGenerator toXmlGenerator) throws IOException {
toXmlGenerator.writeStartObject();
toXmlGenerator.setNextIsAttribute(true);
toXmlGenerator.writeFieldName("type");
toXmlGenerator.writeString(value.getType());
toXmlGenerator.setNextIsAttribute(false);
toXmlGenerator.writeRaw(value.getMaritalStatus());
toXmlGenerator.writeEndObject();
}
#Override
public boolean isEmpty(SerializerProvider provider, User.Status value) {
return value == null || value.getMaritalStatus() == null;
}
}
Since now, you can remove extra XML annotations and register custom serialiser:
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
#Data
class User implements Serializable {
private static final long serialVersionUID = 1L;
private Status status;
private String name;
#Builder
#Value
#JsonSerialize(using = UserStatusJsonSerializer.class)
public static class Status {
private String maritalStatus;
private String type = "text";
}
}
Simple console app usage could look like below:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Value;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
List<User> users = Arrays.asList(
createUser("John", "married"),
createUser("Tom", null));
ObjectMapper jsonMapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.build();
for (User user : users) {
System.out.println(jsonMapper.writeValueAsString(user));
System.out.println();
}
XmlMapper xmlMapper = XmlMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.build();
for (User user : users) {
System.out.println(xmlMapper.writeValueAsString(user));
System.out.println();
}
}
private static User createUser(String name, String maritalStatus) {
return User.builder()
.name(name)
.status(User.Status.builder()
.maritalStatus(maritalStatus)
.build())
.build();
}
}
Above code prints
JSON for John:
{
"status" : "married",
"name" : "John"
}
JSON for Tom:
{
"name" : "Tom"
}
XML for John:
<User>
<status type="text">married</status>
<name>John</name>
</User>
XML for Tom
<User>
<name>Tom</name>
</User>
Notice, that we implemented UserStatusJsonSerializer#isEmpty method which defines what empty means for a Status class. Now, we need to enable JsonInclude.Include.NON_EMPTY feature in your Spring Boot application. Add below key to your application configuration file:
spring.jackson.default-property-inclusion=non_empty
If you do not want to enable inclusion globally you can enable it only for one property using #JsonInclude annotation.
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Status status;
See also:
Using Jackson to add XML attributes to manually-built node-tree
How to tell Jackson to ignore a field during serialization if its value is null?
Spring Boot: Customize the Jackson ObjectMapper
The solution to marshalling an object one way in XML, but another in JSON (different fields, etc.) was to use "mixins".
One trick is that you have to manually register the mixin, there's no magic. See below.
Mixin interface:
public interface UserStatusXmlMixin {
#JsonValue(false)
#JacksonXmlText
String getStatus();
#JacksonXmlProperty(isAttribute = true)
String getType();
}
Implementation:
#Value
public class UserStatus implements UserStatusXmlMixin {
private String status;
#JsonValue
#Override
public String getStatus() {
return status;
}
#Override
public String getType() {
return "text";
}
/**
* Returns an unmodifiable UserStatus when status is available,
* otherwise return null. This will help to remove this object from the responses.
*/
public static UserStatus of(final String status) {
return Optional.ofNullable(status)
.map(UserStatus::new)
.orElse(null);
}
}
I also had to register the "mixin" manually.
#Configuration
public class AppJacksonModule extends SimpleModule {
private static final long serialVersionUID = -1;
private final Map<Class, Class> mixinByTarget;
/**
* Construct an AppJacksonModule.
*/
public AppJacksonModule() {
super("AppJacksonModule");
this.mixinByTarget = Map.of(
UserStatus.class, UserStatusXmlMixin.class
);
}
#Override
public void setupModule(final SetupContext context) {
super.setupModule(context);
final ObjectCodec contextOwner = context.getOwner();
if (contextOwner instanceof XmlMapper) {
mixinByTarget.forEach(context::setMixInAnnotations);
}
}
Now wherever I needed to create UserStatus using UserStatus.of(..) if the input param is null, <status/> won't show up in the response.
I have a situation where I have to implement post method to handle form-data where json are values of keys. Each JSON internally represent an object.
I can get the json as string via RequestParam and then convert to object using Jackson.
#RequestMapping(value = "/rest/patient", consumes = {"multipart/form-data"},
produces = "application/json", method= RequestMethod.POST)
public ResponseEntity<?> savePatient(#RequestParam("patient") String patient ) {
// convert to Patient instance using Jackson
}
Is there any out of box mapping from Spring boot?
I don't believe there is an out of the box mapping.
You could add a GenericConvertor to the WebConversionService that the WebDataBinder uses. You would need to list all your object types. Something like the following:
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
#Component
public class JsonFieldConverter implements GenericConverter {
#Autowired
private ObjectMapper objectMapper;
// Add a new ConvertiblePair for each type you want to convert json
// in a field to using the objectMapper. This example has Patient and Doctor
private static Set<ConvertiblePair> convertibleTypes = new HashSet<>();
static {
convertibleTypes.add(new ConvertiblePair(String.class, Patient.class));
convertibleTypes.add(new ConvertiblePair(String.class, Doctor.class));
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return convertibleTypes;
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return objectMapper.readValue(source.toString(), targetType.getType());
} catch (Exception e) {
// TODO deal with the error.
return source;
}
}
}
And an #ControllerAdvice to plug it into the data binder:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
#ControllerAdvice
public class JsonFieldConfig {
#Autowired
private JsonFieldConverter jsonFieldConverter;
#InitBinder
private void bindMyCustomValidator(WebDataBinder binder) {
((WebConversionService)binder.getConversionService()).addConverter(jsonFieldConverter);
}
}
I am using Jersey rest services with Jackson API for conversion of JSON String to POJOs. All the member variables of the POJO class need to be validated. I already have a framework in place for validation.
What I want to know is if there is any callback method or mechanism which can call my validation API post the JSON to POJO conversion itself. Doing this would make my job easier as I will not have to call the API at all the places in my Rest service class.
public class MyPojoClass{
private int interestRateCode;
private String name;
//just edited
private List<TestDTO> testObjs;
//Psuedo code
//#PostDeserialization
public String callbackMethod(Object obj){
if(!ValidationAPI.validate(obj))
return "false";
}
}
The TestDTO:
public class TestDTO {
private int var1;
private String stringVar;
public TestDTO() {
System.out.println("This constructor does get called every time");
}
}
Is there any annotation like PostDeserialization to achieve this. This will help me to make every POJO class having only one callback method for validation.
The JSON I am passing is
{"interestRateCode": 101,"name": "T",
"testObjs": [
{"var1" :10, "stringVar": "Arunabh"},
{"var1" :15, "stringVar": "Hejib"}
]}
Anyone who can help me on this problem? Thanks for any help.
One thing you can do is use a Request Filter. In the filter you would:
Get the resource Method using the injected ResourceInfo
Get the entity class by traversing the Method Parameters and checking for the method parameter without any annotations. Unless you're using bean validation where #Valid is used next to the parameter, then the entity parameter always is the parameter with no annotations. This is how we determine the entity parameter. From the parameter, we get the class.
Get the entity objects from the request.
From the entity class, using from reflection, find the Method with the #PostDeserialization annotation.
Call the method using reflection.
Below is a complete test. The ValidationFilter is the class with previous mentioned steps.
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.junit.Assert.assertEquals;
/**
* Run like any other JUnit test. A couple required dependencies.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>${jersey2.version}</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-json-jackson</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*/
public class PostDeserializationTest extends JerseyTest {
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PostDeserialization {}
public static class ValidationError extends RuntimeException {
public ValidationError(String message) {
super(message);
}
}
public static class ValidationErrorMapper
implements ExceptionMapper<ValidationError> {
#Override
public Response toResponse(ValidationError ex) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.getMessage())
.build();
}
}
public static class Bean {
public String value;
#PostDeserialization
public void validate() {
if (!"expected".equals(value)) {
throw new ValidationError("value must be 'expected'");
}
}
}
public static class ValidationFilter implements ContainerRequestFilter {
#Context
private ResourceInfo info;
#Override
public void filter(ContainerRequestContext request) throws IOException {
Class<?> entityClass = getEntityClass();
if (entityClass != null) {
final ContainerRequest cr = (ContainerRequest) request;
cr.bufferEntity();
final Object entity = cr.readEntity(entityClass);
findMethodAndValidate(entity);
}
}
private Class<?> getEntityClass() {
final Method rm = info.getResourceMethod();
final Annotation[][] paramAnnotations = rm.getParameterAnnotations();
for (int i = 0; i < paramAnnotations.length; i++) {
// entity parameters have no annotations.
if (paramAnnotations[i].length == 0) {
return rm.getParameterTypes()[i];
}
}
return null;
}
private void findMethodAndValidate(Object entity) {
final Method[] methods = entity.getClass().getMethods();
for (Method method: methods) {
if (method.isAnnotationPresent(PostDeserialization.class)) {
// validation method should take no parameters.
if (method.getParameterCount() != 0) {
throw new RuntimeException(
"Validation method must not have parameters.");
}
try {
method.invoke(entity);
} catch (InvocationTargetException ex) {
// if an exception happens during invocation,
// an InvocationException is thrown. We want the cause,
// expecting it to be a ValidationError.
Throwable cause = ex.getCause();
if (cause instanceof ValidationError) {
throw (ValidationError) cause;
} else {
throw new RuntimeException(ex);
}
} catch (Exception ex) {
throw new RuntimeException(
"Error calling validation method.", ex);
}
}
}
}
}
#Path("test")
public static class TestResource {
#POST
#Consumes("application/json")
public String post(Bean bean) {
return bean.value;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(ValidationErrorMapper.class)
.register(ValidationFilter.class)
.register(new ExceptionMapper<Throwable>() {
#Override
public Response toResponse(Throwable t) {
t.printStackTrace();
return Response.serverError()
.entity(t.getMessage()).build();
}
});
}
#Test
public void testValidationError() {
final Bean bean = new Bean();
bean.value = "not expected";
final Response response = target("test")
.request()
.post(Entity.json(bean));
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
assertEquals("value must be 'expected'", response.readEntity(String.class));
}
#Test
public void testNoValidationError() {
final Bean bean = new Bean();
bean.value = "expected";
final Response response = target("test")
.request()
.post(Entity.json(bean));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
assertEquals("expected", response.readEntity(String.class));
}
}
I would like to achieve the opposite of this:
Gson serialize POJO with root value included?
I get a JSON String
{"RootDTO":{"classField_01":"value"}}
and would like to deserialise that to
class RootDTO {
String classField_01;
//nice getter and setter are included
}
I know how to set the parameter so that in serialisation the root element is included into the JSON string.
But how to deserialise that now? Searching like hell already but wasn't able to find a suitable answer.
Thank you for any ideas!
make RootDTO.java
import com.google.gson.annotations.SerializedName;
public class RootDTO {
#SerializedName("classField_01")
private String classField_01;
public String getClassField_01() {
return classField_01;
}
public void setClassField_01(String classField_01) {
this.classField_01 = classField_01;
}
}
make Response.java
import com.google.gson.annotations.SerializedName;
public class Response {
#SerializedName("RootDTO")
private RootDTO rootDTO;
public RootDTO getRootDTO() {
return rootDTO;
}
public void setRootDTO(RootDTO rootDTO) {
this.rootDTO = rootDTO;
}
}
test this code to generate your json data
import com.google.gson.Gson;
public class TestOne {
public static void main(String[] args) {
RootDTO dto = new RootDTO();
dto.setClassField_01("value");
Response response = new Response();
response.setRootDTO(dto);
String result = (new Gson()).toJson(response);
System.out.println(""+result);
}
}
output generated with this code
{"RootDTO":{"classField_01":"value"}}