How to produce error response in json - json

Am writing a Restful Webservice Impl, where i consume and produce response in JSON format by annotating #Produces("application/json"). Am producing JSON response as well. Here am handling exception with a class where it has error code and error message. When am getting exception it is not produced in application/json format. I used ExceptionMapper to find a solution but it is `text/plain format.
snippet
public Class Confiuration{
#Path("getData")
#Consumes("application/json")
#Produces("application/json")
public JSONGetDataResponseVo getData(GetDataRequestVo datarequestVO)
throws FaultResponse {
JSONGetDataResponseVo response=new JSONGetDataResponseVo ();
DataServiceValidator.validateGetConfigurationAndDataRequest(datarequestVO);
....
....
}catch(ApplicationException applicationException){
throw new FaultResponse(applicationException,locale);
}
}
FaultResponseMapper
#Provider
public class FaultResponseMapper implements ExceptionMapper<FaultResponse> {
#Context
private HttpHeaders headers;
public Response toResponse(FaultResponse faultResponse) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(faultResponse).type(MediaType.APPLICATION_JSON).build();
}
}
Application Exception
public abstract class ApplicationException extends Exception{
private java.lang.String errorCode;
public ApplicationException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public ApplicationException(String message) {
super(message);
}
public java.lang.String getErrorCode() {
return this.errorCode;
}
public abstract String getLocaleMessage(Locale locale);
}
FaultResponse
public class FaultResponse extends WebApplicationException {
private String errorCode;
private String errorMessage;
private String localErrorMessage;
public FaultResponse(String errorCode, String errorMessage,
String localErrorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.localErrorMessage = localErrorMessage;
}
public FaultResponse(ApplicationException applicationException,
Locale locale) {
this.errorCode = applicationException.getErrorCode();
this.errorMessage = applicationException.getMessage();
if (locale != null
&& applicationException.getLocaleMessage(locale) != null) {
this.localErrorMessage = applicationException
.getLocaleMessage(locale);
} else {
this.localErrorMessage = applicationException.getMessage();
}
}
}
So here how can i produce my faultResponse in JSON format.

This has to do with the fact that you are returning an exception as a response. I would
Make an exception mapper for ApplicationException.
Refactor FaultResponse to not extend and exception. Just create it in the mapper.
In order to see the response, you will need to send a status other than No Content. You can't have a body in it. Send somethng like Bad Request.
You can just declare the resource method as throws ApplicationException. You don't need to catch it and rethrow.
I've made these changes, and it works fine.
UPDATE: with complete test
Added getters (required for marshalling) to FaultResponse and remove the exception extension
public class FaultResponse {
...
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public String getLocalErrorMessage() { return localErrorMessage; }
...
}
Created a Service for testing and ApplicationException implementation
public class ApplicationExceptionImpl extends ApplicationException {
public ApplicationExceptionImpl(){
this("400", "Bad Request");
}
public ApplicationExceptionImpl(String errorCode, String message) {
super(errorCode, message);
}
#Override
public String getLocaleMessage(Locale locale) {
return "Bad Request";
}
}
public class FaultService {
public void doSomething() throws ApplicationException {
throw new ApplicationExceptionImpl();
}
}
Resource class
#Path("fault")
public class FaultResource {
FaultService service = new FaultService();
#GET
public Response getException() throws ApplicationException {
service.doSomething();
return Response.ok("Cool").build();
}
}
ExceptionMapper
#Provider
public class ApplicationExceptionMapper implements ExceptionMapper<ApplicationException> {
#Override
public Response toResponse(ApplicationException exception) {
FaultResponse response = new FaultResponse(exception, Locale.ENGLISH);
return Response.status(Response.Status.BAD_REQUEST)
.entity(response).type(MediaType.APPLICATION_JSON).build();
}
}
ApplicationException class is left the same
curl -v http://localhost:8080/api/fault
{"errorCode":"400","errorMessage":"Bad Request","localErrorMessage":"Bad Request"}
If after this you are still not seeing JSON, it's possible you do not have a provider configured. If this is the case, please show your application configuration, along with your project dependencies.

Related

I am not able to handle the Exception using JAX-RS

This the NullPointerExceptionMapper class
package com.sample.Exceptionhandler;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import response.Message;
#Provider
public class NullPointerExceptionMapper implements ExceptionMapper<NullPointerException> {
public Response toResponse(NullPointerException ex) {
Message message=new Message(500,ex.getMessage(),200);
return Response.status(Status.NOT_FOUND).entity(message)
.build();
}
}
This is the Message class
package response;
public class Message {
private int status;
private String message;
private int code;
public Message() {
}
public Message(int status, String message, int code) {
super();
this.status = status;
this.message = message;
this.code = code;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
This is the Controller method
#RequestMapping(value = "/getOrder", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<PurchaseOrderHeaderDto> getOrders(){
String exceptionOccured="NULL_POINTER";
if(exceptionOccured.equalsIgnoreCase("NULL_POINTER")){
throw new NullPointerException("Null Pointer Exception");
}
return purchaseImpl.GetPurchaseOrder();
}
I am not able to handle the Exception thrown and I am using JAX-RS for handling it, but it is not working properly any suggestions are welcomed that would help me sort this out.
You're mixing JAX-RS with Spring MVC. Your controller code is Spring MVC and your attempt at exception handling with the ExceptionMapper is JAX-RS. These are two completely different and incompatible frameworks. For Spring MVC, you want to use a #ControllerAdvice class.

How do I customize default error message from spring #Valid validation?

DTO:
public class User {
#NotNull
private String name;
#NotNull
private String password;
//..
}
Controller:
#RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(#Valid #RequestBody User user) {
//..
return new ResponseEntity<>(HttpStatus.OK);
}
Default json error:
{"timestamp":1417379464584,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public org.springframework.http.ResponseEntity<demo.User> demo.UserController.saveUser(demo.User), with 2 error(s): [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [may not be null]],"path":"/user"}
I would like to have my custom json for each error occured. How do I accomplish that?
If you want full control over the response message in every controller write a ControllerAdvice. For example, that example transform MethodArgumentNotValidException into a custom json object:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* Kudos http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
*
*/
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MethodArgumentNotValidExceptionHandler {
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public Error methodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private Error processFieldErrors(List<org.springframework.validation.FieldError> fieldErrors) {
Error error = new Error(BAD_REQUEST.value(), "validation error");
for (org.springframework.validation.FieldError fieldError: fieldErrors) {
error.addFieldError(fieldError.getField(), fieldError.getDefaultMessage());
}
return error;
}
static class Error {
private final int status;
private final String message;
private List<FieldError> fieldErrors = new ArrayList<>();
Error(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
public void addFieldError(String path, String message) {
FieldError error = new FieldError(path, message);
fieldErrors.add(error);
}
public List<FieldError> getFieldErrors() {
return fieldErrors;
}
}
}
You can perform validation with Errors/BindingResult object.
Add Errors argument to your controller method and customize the error message when errors found.
Below is the sample example, errors.hasErrors() returns true when validation is failed.
#RequestMapping(value = "/user", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> saveUser(#Valid #RequestBody User user, Errors errors) {
if (errors.hasErrors()) {
return new ResponseEntity(new ApiErrors(errors), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
I know this is kind of old question,
But I just run into it and I found some pretty good article which has also a perfect example in github.
Basically it uses #ControllerAdvice as Spring documentation suggests.
So for example catching 400 error will be achieved by overriding one function:
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
logger.info(ex.getClass().getName());
//
final List<String> errors = new ArrayList<String>();
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.add(error.getField() + ": " + error.getDefaultMessage());
}
for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
}
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
}
}
(ApiError class is a simple object to hold status, message, errors)
One way to do it is adding message in #NotNull annotation on entity properties. And adding #Valid annotation in controller request body.
DTO:
public class User {
#NotNull(message = "User name cannot be empty")
private String name;
#NotNull(message = "Password cannot be empty")
private String password;
//..
}
Controller:
#RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<String> saveUser(#Valid #RequestBody User user) {
//..
return new ResponseEntity<>(HttpStatus.OK);
}
// Add one
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<YourErrorResponse>> handleException(MethodArgumentNotValidException ex) {
// Loop through FieldErrors in ex.getBindingResult();
// return *YourErrorReponse* filled using *fieldErrors*
}
#ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandler implements ApplicationContextAware {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public ApplicationError validationException(MethodArgumentNotValidException e) {
e.printStackTrace();
return new ApplicationError(SysMessageEnum.MSG_005, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
}
You can do something like this
#ExceptionHandler(value = MethodArgumentNotValidException.class)
protected ResponseEntity<Error> handleGlobalExceptions(MethodArgumentNotValidException ex,
WebRequest request) {
log.catching(ex);
return new ResponseEntity<>(createErrorResp(HttpStatus.BAD_REQUEST,
ex.getBindingResult().getFieldErrors().stream().map(err -> err.getDefaultMessage())
.collect(java.util.stream.Collectors.joining(", "))),
HttpStatus.BAD_REQUEST);
}
For customized the error message in JSON format then do the below steps.
- Create one #Component called CommonErrorHandler
#Component
public class CommonErrorHandler {
public Map<String,Object> getFieldErrorResponse(BindingResult result){
Map<String, Object> fielderror = new HashMap<>();
List<FieldError>errors= result.getFieldErrors();
for (FieldError error : errors) {
fielderror.put(error.getField(), error.getDefaultMessage());
}return fielderror;
}
public ResponseEntity<Object> fieldErrorResponse(String message,Object fieldError){
Map<String, Object> map = new HashMap<>();
map.put("isSuccess", false);
map.put("data", null);
map.put("status", HttpStatus.BAD_REQUEST);
map.put("message", message);
map.put("timeStamp", DateUtils.getSysDate());
map.put("filedError", fieldError);
return new ResponseEntity<Object>(map,HttpStatus.BAD_REQUEST);
}
}
-- Add InvalidException class
public class InvalidDataException extends RuntimeException {
/**
* #author Ashok Parmar
*/
private static final long serialVersionUID = -4164793146536667139L;
private BindingResult result;
public InvalidDataException(BindingResult result) {
super();
this.setResult(result);
}
public BindingResult getResult() {
return result;
}
public void setResult(BindingResult result) {
this.result = result;
}
}
- Introduce #ControllerAdvice class
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(InvalidDataException.class)
public ResponseEntity<?> invalidDataException(InvalidDataException ex, WebRequest request) {
List<FieldError> errors = ex.getResult().getFieldErrors();
for (FieldError error : errors) {
logger.error("Filed Name ::: " + error.getField() + "Error Message :::" + error.getDefaultMessage());
}
return commonErrorHandler.fieldErrorResponse("Error", commonErrorHandler.getFieldErrorResponse(ex.getResult()));
}
}
-- Use in controller with #Valid and throw exception
public AnyBeans update(**#Valid** #RequestBody AnyBeans anyBeans ,
BindingResult result) {
AnyBeans resultStr = null;
if (result.hasErrors()) {
**throw new InvalidDataException(result);**
} else {
resultStr = anyBeansService.(anyBeans );
return resultStr;
}
}
-- Output will be in JSON format
{
"timeStamp": 1590500231932,
"data": null,
"message": "Error",
"isSuccess": false,
"status": "BAD_REQUEST",
"filedError": {
"name": "Name is mandatory"
}
}
Hope this will be work. :-D
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// ex.getBindingResult(): extract the bind result for default message.
String errorResult = ex.getBindingResult().toString();
CustomizedExceptionHandlerResponse exceptionResponse = new CustomizedExceptionHandlerResponse(
errorResult, new Date(), request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
}
}
class CustomizedExceptionHandlerResponse {
private String message;
private String status;
private Date timestamp;
// constuctor, setters, getters...
}
you can use this code to iterate through errors and build a custom error message :
import lombok.Data;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.validation.ConstraintViolation;
import java.util.List;
import java.util.stream.Collectors;
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDto> handleException(MethodArgumentNotValidException ex) {
ErrorDto dto = new ErrorDto(HttpStatus.BAD_REQUEST, "Validation error");
dto.setDetailedMessages(ex.getBindingResult().getAllErrors().stream()
.map(err -> err.unwrap(ConstraintViolation.class))
.map(err -> String.format("'%s' %s", err.getPropertyPath(), err.getMessage()))
.collect(Collectors.toList()));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(dto);
}
#Data
public static class ErrorDto {
private final int status;
private final String error;
private final String message;
private List<String> detailedMessages;
public ErrorDto(HttpStatus httpStatus, String message) {
status = httpStatus.value();
error = httpStatus.getReasonPhrase();
this.message = message;
}
}
}
This will give you a response like this in case of error :
{
"status": 400,
"error": "Bad Request",
"message": "Validation error",
"detailedMessages": [
"'yourField' should not be empty."
]
}
Add some information too.
If you use just #Valid, you need to catch BindException. If you use #Valid #RequestBody catch MethodArgumentNotValidException
Some sources:
HandlerMethodArgumentResolverComposite.getArgumentResolver(MethodParameter parameter):129 - search which HandlerMethodArgumentResolver support such parameter
RequestResponseBodyMethodProcessor.supportsParameter(MethodParameter parameter) - return true if parameter has annotation #RequestBody
RequestResponseBodyMethodProcessor:139 - throw MethodArgumentNotValidException
ModelAttributeMethodProcessor:164 - throw BindException
I know this is an old question, but i stumbled across and decided to show a cleaner way of doing what Ksokol showed.
StandardError class:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class StandardError {
private Instant timestamp;
private Integer status;
private String error;
private String message;
private String path;
}
ControllerExceptionHandler class:
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<StandardError> notValid(MethodArgumentNotValidException e, HttpServletRequest request) {
String error = "Dados inválidos.";
HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY;
StandardError err = new StandardError(
Instant.now(),
status.value(),
error,
e.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toSet())
.toString()
.replaceAll("\\[*]*", ""),
request.getRequestURI()
);
return ResponseEntity.status(status).body(err);
}
This way if you have a custom message on your bean validator, it will appear formatted in a error.
Result:

Unable to get through CXF with JSON

I am trying to build a CXF RESTFul service with JSON as input and output. I am using JAXRSServerFactoryBean to boot my service. When I try to hit the URL from a client program, I am getting the following exception. My program is very simple and attached the same at the bottom.
Please help.
May 19, 2015 11:03:30 PM org.apache.cxf.jaxrs.provider.AbstractJAXBProvider handleExceptionStart
WARNING: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[0,0]
Message: A JSONObject text must begin with '{' at character 0 of
at org.codehaus.jettison.mapped.MappedXMLInputFactory.createXMLStreamReader(MappedXMLInputFactory.java:51)
at org.codehaus.jettison.AbstractXMLInputFactory.createXMLStreamReader(AbstractXMLInputFactory.java:116)
at org.apache.cxf.jaxrs.provider.json.utils.JSONUtils.createStreamReader(JSONUtils.java:162)
at org.apache.cxf.jaxrs.provider.json.JSONProvider.createReader(JSONProvider.java:290)
at org.apache.cxf.jaxrs.provider.json.JSONProvider.createReader(JSONProvider.java:280)
at org.apache.cxf.jaxrs.provider.json.JSONProvider.readFrom(JSONProvider.java:233)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1337)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBody(JAXRSUtils.java:1288)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.processParameter(JAXRSUtils.java:824)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.processParameters(JAXRSUtils.java:787)
at org.apache.cxf.jaxrs.interceptor.JAXRSInInterceptor.processRequest(JAXRSInInterceptor.java:212)
at org.apache.cxf.jaxrs.interceptor.JAXRSInInterceptor.handleMessage(JAXRSInInterceptor.java:77)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:307)
May 19, 2015 11:03:30 PM org.apache.cxf.jaxrs.impl.WebApplicationExceptionMapper toResponse
WARNING: javax.ws.rs.BadRequestException: HTTP 400 Bad Request
at org.apache.cxf.jaxrs.utils.SpecExceptions.toBadRequestException(SpecExceptions.java:84)
at org.apache.cxf.jaxrs.utils.ExceptionUtils.toBadRequestException(ExceptionUtils.java:114)
at org.apache.cxf.jaxrs.provider.AbstractJAXBProvider.handleExceptionEnd(AbstractJAXBProvider.java:705)
at org.apache.cxf.jaxrs.provider.AbstractJAXBProvider.handleXMLStreamException(AbstractJAXBProvider.java:734)
at org.apache.cxf.jaxrs.provider.json.JSONProvider.readFrom(JSONProvider.java:261)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1337)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBody(JAXRSUtils.java:1288)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.processParameter(JAXRSUtils.java:824)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.processParameters(JAXRSUtils.java:787)
at org.apache.cxf.jaxrs.interceptor.JAXRSInInterceptor.processRequest(JAXRSInInterceptor.java:212)
at org.apache.cxf.jaxrs.interceptor.JAXRSInInterceptor.handleMessage(JAXRSInInterceptor.java:77)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:307)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:251)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.doService(JettyHTTPDestination.java:234)
at org.apache.cxf.transport.http_jetty.JettyHTTPHandler.handle(JettyHTTPHandler.java:70)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1129)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1065)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
Caused by: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[0,0]
Message: A JSONObject text must begin with '{' at character 0 of
at org.codehaus.jettison.mapped.MappedXMLInputFactory.createXMLStreamReader(MappedXMLInputFactory.java:51)
at org.codehaus.jettison.AbstractXMLInputFactory.createXMLStreamReader(AbstractXMLInputFactory.java:116)
at org.apache.cxf.jaxrs.provider.json.utils.JSONUtils.createStreamReader(JSONUtils.java:162)
at org.apache.cxf.jaxrs.provider.json.JSONProvider.createReader(JSONProvider.java:290)
RestFulServiceStarter
public class RestFulServiceStarter {
public static void main(String[] args) {
JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
sf.setResourceClasses(ProfileService.class);
sf.setResourceProvider(ProfileService.class,
new SingletonResourceProvider(new ProfileServiceImpl()));
sf.setAddress("http://localhost:9999/");
Server server = sf.create();
}
}
ProfileService
#Path("/profile/")
public interface ProfileService {
#GET
#Path("/static/")
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
public Response getStaticProfiles(ProfileRequest pr);
}
ProfileServiceImpl
public class ProfileServiceImpl implements ProfileService {
public Response getStaticProfiles(ProfileRequest pr) {
return Response.status(200).entity(pr).build();
}
}
ProfileRequest
#XmlRootElement ( name = "profile" )
public class ProfileRequest {
private String name="";
private String country="";
private String region="";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
}
Your interface method is annotated with a #GET method, yet it also has an entity parameter (ProfileRequest). Try #POST.

Simple way to strip outer array of responses in gson

I'm working with an api (Phillips Hue) that wraps all of it's json responses in an array with one entry (the content).
Example:
[{
"error": {
"type": 5,
"address": "/",
"description": "invalid/missing parameters in body"
}
}]
I usually write standard POJO's parsed by GSON to handle responses but since the response is not a json object I'm a bit stumped on the best way to deal with this. I didn't really want every object to actually be an array that I have to call .get(0) on.
Example of the POJO if it was a JSON obj and NOT wrapped in an array.
public class DeviceUserResponse {
private DeviceUser success;
private Error error;
public DeviceUser getSuccess() {
return success;
}
public Error getError() {
return error;
}
public static class Error {
private int type;
private String address;
private String description;
public int getType() {
return type;
}
public String getAddress() {
return address;
}
public String getDescription() {
return description;
}
#Override
public String toString() {
return "Type: " + this.type
+ " Address: " + this.address
+ " Description: " + this.description;
}
}
}
What I have to do right now:
ArrayList<DeviceUserResponse> response.get(0).getError();
Is there a way that I can strip this array for every response or am I just going to have to do a .get(0) in my POJO's and just not expose it?
I think you've to go with custom deserialization in order to "strip out" the array.
Here a possible solution.
An adapter for your response POJO:
public class DeviceUserResponseAdapter extends TypeAdapter<DeviceUserResponse> {
protected TypeAdapter<DeviceUserResponse> defaultAdapter;
public DeviceUserResponseAdapter(TypeAdapter<DeviceUserResponse> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
#Override
public void write(JsonWriter out, DeviceUserResponse value) throws IOException {
defaultAdapter.write(out, value);
}
#Override
public DeviceUserResponse read(JsonReader in) throws IOException {
in.beginArray();
assert(in.hasNext());
DeviceUserResponse response = defaultAdapter.read(in);
in.endArray();
return response;
}
}
A factory for your adapter:
public class DeviceUserResponseAdapterFactory implements TypeAdapterFactory {
#Override
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType()!=DeviceUserResponse.class) return null;
TypeAdapter<DeviceUserResponse> defaultAdapter = (TypeAdapter<DeviceUserResponse>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new DeviceUserResponseAdapter(defaultAdapter);
}
}
Then you've to register and user it:
DeviceUserResponseAdapterFactory adapterFactory = new DeviceUserResponseAdapterFactory();
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.registerTypeAdapterFactory(adapterFactory).create();
DeviceUserResponse response = gson.fromJson(json, DeviceUserResponse.class);
System.out.println(response.getError());
This solution will not work if you have the DeviceUserResponse inside other complex JSON object. I that case the adapter will try to find an array and will terminate with an error.
Another solution is to parse it as array and then in your "communication" layer you get only the first element. This will preserve the GSon deserialization.
In the comment you're asking for a more generic solution, here one:
The adapter:
public class ResponseAdapter<T> extends TypeAdapter<T> {
protected TypeAdapter<T> defaultAdapter;
public ResponseAdapter(TypeAdapter<T> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
#Override
public void write(JsonWriter out, T value) throws IOException {
defaultAdapter.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
in.beginArray();
assert(in.hasNext());
T response = defaultAdapter.read(in);
in.endArray();
return response;
}
}
The factory:
public class ResponseAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if ((type.getRawType().getSuperclass() != Response.class)) return null;
TypeAdapter<T> defaultAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new ResponseAdapter<T>(defaultAdapter);
}
}
Where Response.class is your super class from which all the service responses inherit.
The first solution advices are still valid.

Jersey unmarshal JSON: Last element null does not work

I am using Jersey to parse the following JSON:
{"response":{"status":"OK","campaigns":[{"id":12345,"state":"active","code":null}]}}
But I get the following error message:
java.lang.IllegalArgumentException: No more parsing elements.
If I switch the position of the fields code and state so that the resulting JSON looks like
{"response":{"status":"OK","campaigns":[{"id":12345,"code":null,"state":"active"}]}}
everything works fine. Also if I change the code-field in the first JSON to a non-null value like "code":"test", Jersey can parse this without any problems. I tried other more complex examples always getting the above mentioned error message when leaving the last field of any element of an array null.
I think I am doing something wrong, because I could not find any others having the similar problem. I already tried to implement a CustomJAXBContextResolver using other JSON notations like natural but nothing worked for me.
Any ideas?
Here are my binding classes:
#XmlRootElement
public class LoadEntityResponse {
public LoadEntityResponse() {
}
private Response response;
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
}
and
public class Response {
public Response() {
}
private String status;
private String error;
private String error_id;
private Campaign[] campaigns;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getError_id() {
return error_id;
}
public void setError_id(String error_id) {
this.error_id = error_id;
}
public Campaign[] getCampaigns() {
return campaigns;
}
public void setCampaigns(Campaign[] campaigns) {
this.campaigns = campaigns;
}
}
and finally
public class Campaign{
public Campaign() {
}
protected int id;
protected String code;
protected String state;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
Solved: Using JacksonJsonProvider now:
...
DefaultClientConfig config = new DefaultClientConfig();
config.getClasses().add(JacksonJsonProvider.class);
...
that´s all!
You can also use Jackson POJO support that comes with jersey-json but there is a need to do some configuration, see POJO support in Jersey User Guide.
Try using Genson http://code.google.com/p/genson/.
To enable it on client side use the following code:
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(GensonJsonConverter.class);
cli = Client.create(config);
EDIT: on server side there is no configuration needed, when the jar is in your classpath json support is automatically enabled.