I have a Spring Rest Controller and a "command"/DTO object for the POST method on the controller. I also wrote a serializer / deserializer for one of the fields "due" - which is a Calendar object.
Since Jackson2 dependencies are defined in pom.xml, I expect Spring to detect my deserializer and use it to convert a String input to java.util.Calendar. However, I get a "no matching editors or conversion strategy found" exception. My serializer is working ... only the deserializer won't work!
Rest Controller (TaskController.java)
#Controller
public class TasksController {
...
#RequestMapping(value = "/tasks", method = RequestMethod.POST)
public #ResponseBody Task createTask(#Valid TasksCommand tasksCommand){
Task task = new Task();
task.setName(tasksCommand.getName());
task.setDue(tasksCommand.getDue());
task.setCategory(tasksCommand.getCategory());
return task;
}
}
The command / dto object:
public class TasksCommand {
#NotBlank
#NotNull
private String name;
#JsonDeserialize(using = CalendarDeserializer.class)
#JsonSerialize(using = CalendarSerializer.class)
private Calendar due;
private String category;
... getters & setters ...
}
Serializer for Calendar - for my custom date format
public class CalendarSerializer extends JsonSerializer<Calendar>{
#Override
public void serialize(Calendar calendar, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss Z"); // Z - RFC 822 time zone
jgen.writeString(simpleDateFormat.format(calendar.getTime()));
}
}
Deserializer for Calendar
public class CalendarDeserializer extends JsonDeserializer<Calendar> {
private static Logger logger = Logger.getLogger(CalendarDeserializer.class);
#Override
public Calendar deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String dateStr = jsonParser.getText();
logger.info("deserializing date:" + dateStr);
return Calendar.getInstance();
}
}
I have Jackson2 dependencies specified in Maven. When doing a GET (code not shown here), the serializer gets invoked correctly, and I see a JSON output of the "Task" object.
However, when I make an HTTP POST as below
curl -X POST -d name=task1 -d category=weekly -d due=01/01/2013 http://localhost:8080/tasks
the deserializer is never detected and I get an exception
Failed to convert property value of type java.lang.String to required type java.util.Calendar for property due; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Calendar] for property due: no matching editors or conversion strategy found
Since the serializer is getting detected, I don't understand why the deserializer is not detected and invoked correctly. From what I have read, having Jackson2 library on classpath will be detected by Spring and it would automatically add MappingJackson2HttpMessageConverter to the list of message converters. So this code should detect the string in HTTP POST and convert it to java.util.Calendar using the Deserializer. What am I missing?
Figured this one out.
When making an HTTP POST using Curl as shown in the description (with the -d flag), the POST comes in with content type "application/x-www-form-urlencoded" (obvious in hindsight, since it's treated as a web form data). This triggers the Spring FormHttpMessageConverter message converter and MappingJackson2HttpMessageConverter converter is never triggered.
However, the serializer and deserializer are annotated with Jackson annotations and only MappingJackson2HttpMessageConverter knows how to handle that. This is what causes the exception described in the problem.
The solution is to pass in JSON data in the curl command:
curl -X POST -H "Content-Type: application/json" -d '{"name":"test1","due":"1/1/2013","category":"weekly"}' http://localhost:8080/tasks
Also, since the JSON input comes in the body of the HTTP POST call, we need to use the #RequestBody annotation in the Controller method signature.
#RequestMapping(value = "/tasks", method = RequestMethod.POST)
public #ResponseBody Task createTask(#RequestBody #Valid TasksCommand tasksCommand){
Task task = new Task();
task.setName(tasksCommand.getName());
task.setDue(tasksCommand.getDue());
task.setCategory(tasksCommand.getCategory());
return task;
}
Now the deserializer gets triggered and the input due date is converted from String to Calendar object correctly.
Related
my code
#GetMapping(value = {"/metadata"}, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public String getMetadata() {
return dppService.getMetadata();
}
the method getMetadata will just return a json string. it just read data from the json file, and it is in another library can not be changed.
But when call this api, i got the follow reponse:
"{\"Namespace\":\"com.xxx\"...
the json string was escaped.
expected:
"{"Namespace":"com.xxx"...
How could i make it return the right json? BTW, our other services also return a json string in the controller, but their response will not be escaped which is so confused for me.
You could do this two ways:
From what I could understand you are having this issues because you might be returning the json as a string from from the service method dppService.getMetadata() by converting it manually to a string. If so , change that and instead return a POJO class from the service method as well as the controller, spring default jackson converter should automatically convert it to a json when the request is served. (I would suggest you go with this approach)
Another approach (the hacky less desirable one) if you still want to keep returning a string then you could configure the StringMessageConverter like below to accept json:
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
stringConverter.setSupportedMediaTypes(Arrays.asList( //
MediaType.TEXT_PLAIN, //
MediaType.TEXT_HTML, //
MediaType.APPLICATION_JSON));
converters.add(stringConverter);
}
root cause:
There is a configuration file in the project:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(jacksonBuilder().build()));
converters.stream()
.filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(converter -> ((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(UTF_8));
}
This configuration overrite the defualt jackson behavior. There are two ways to solve this issue:
1.Remove this configuration, then it will be the default behavior
2.Add the StringHttpMessageConverter in this configuration, see Ananthapadmanabhan's option2
I am developing a module where i am using rest service to get data. i am not getting how to store JSON using Jackson and store it which has Queryparam also. Any help is really appreciated as I am new to this.I am trying to do server side filtering in extjs infinte grid which is sending the below request to rest service.
When the page load first time, it sends:
http://myhost/mycontext/rest/populateGrid?_dc=9999999999999&page=1&start=0&limit=500
When you select filter on name and place, it sends:
http://myhost/mycontext/rest/populateGrid?_dc=9999999999999&filter=[{"type":"string","value":"Tom","field":"name"},{"type":"string","value":"London","field":"Location"}]&page=1&start=0&limit=500
I am trying to save this in POJO and then sending this to database to retrieve data. For this on rest side I have written something like this:
#Provider
#Path("/rest")
public interface restAccessPoint {
#GET
#Path("/populateGrid")
#Produces({MediaType.APPLICATION_JSON})
public Response getallGridData(FilterJsonToJava filterparam,#QueryParam("page") String page,#QueryParam("start") String start,#QueryParam("limit") String limit);
}
public class FilterJsonToJava {
#JsonProperty(value ="filter")
private List<Filter> data;
.. getter and setter below
}
public class Filter {
#JsonProperty("type")
private String type;
#JsonProperty("value")
private String value;
#JsonProperty("field")
private String field;
...getter and setters below
}
I am getting the below error:
The following warnings have been detected with resource and/or provider classes: WARNING: A HTTP GET method, public abstract javax.ws.rs.core.Response com.xx.xx.xx.xxxxx (com.xx.xx.xx.xx.json.FilterJsonToJava ,java.lang.String,java.lang.String,java.lang.String), should not consume any entity.
com.xx.xx.xx.xx.json.FilterJsonToJava, and Java type class com.xx.xx.xx.FilterJsonToJava, and MIME media type application/octet-stream was not found
[11/6/13 17:46:54:065] 0000001c ContainerRequ E The registered message body readers compatible with the MIME media type are:
application/octet-stream
com.sun.jersey.core.impl.provider.entity.ByteArrayProvider com.sun.jersey.core.impl.provider.entity.FileProvider com.sun.jersey.core.impl.provider.entity.InputStreamProvider com.sun.jersey.core.impl.provider.entity.DataSourceProvider com.sun.jersey.core.impl.provider.entity.RenderedImageProvider */* -> com.sun.jersey.core.impl.provider.entity.FormProvider ...
You should try to do it this way:
Response getallGridData(#QueryParam("filter") String filterparam, ...) {
ObjectMapper mapper = new ObjectMapper();
Filter yourObject = mapper.readValue(filterparam, Filter.class);
}
This is the way, because your payload is in the query parameter. The object injected as it is with POST requests when there is a payload.
I am building a REST service with jersey and I am stuck with a weird exception.
I want to hit a REST uri similar to:
http://localhost:9889/rest/Users/{userid}
the content to be sent with the request is in JSON similar to:
{
"attr1":"name",
"attr2":"age"
}
The endpoint url code is as shwon below:
#Path("/rest/Users")
class Users
{
#GET
#Produces(MediaType.TEXT_PLAIN)
#Path("/{userId}")
#Consumes(MediaType.APPLICATION_JSON)
public String getUserInfoQuery(
QueryDoc reqJSON,
#PathParam("userId") String userId,
#HeaderParam("Session-Token") String sessionId,
#HeaderParam("Authorization") String authToken)
)
{
.
.
.
.
}
}
And QueryDoc resource looks like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
public class QueryDoc
{
#XmlElement(name = "attr1")
private String attr1;
#XmlElement(name = "attr2")
private String attr2;
//getters and setters
.
.
.
.
}
When I am starting the server, an exception is thrown
com.sun.jersey.api.container.ContainerException: Fatal issues found at
class com.test.Users. See logs for more details.
at com.sun.jersey.server.impl.application.WebApplicationImpl.newResourceClass(WebApplicationIm....
I could find this exception here http://www.skybert.net/java/jersey/
and as mentioned in this link..the reason is
public String getUserInfoQuery(
QueryDoc reqJSON,
reqJSON is not being annotated. If I annotate it with some annotation the exception is not thrown when server is started but in this case url response is meaningless. If i remove this parameter the url works but it doesn't consume the request JSON.
How can I make it work where I want to consume JSON content of the request as well as HeaderParams and PathParams
Is your getUserInfoQuery() method annotated with #GET annotation? If so, it is mapped to HTTP GET request. You cannot send entity in HTTP GET, so the unannotated parameter does not make sense (as Jersey maps entity to the unannotated param, but as said, in case of GET there is no entity).
Change your method getUserInfoQuery() to #PUT. In the QueryDoc class remove all annotations except #XmlRootElement. Since the attribute name you pass in the request body is same as the those in QueryDoc #XmlElement is not required. Moreover #XmlElement should be given to ge getter method.This is a good article on ReST with Jersey.
Try this:
I had the same exception with no additional details on Jersey's 'newResourceClass' method;
after hours of debugging, I realized it happened due to ambiguous URIs.
Check your URIs and eliminate any possible duplicates, such as this one:
#Path("/users")
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<String> getUsers() {
...
}
#Path("/users") // BAD
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getUserById(#QueryParam("userId") String userId) {
...
}
I'm attempting to bind json to the model with #RequestBody.
We are validating our model using JSR303.
Here is the method signature
#RequestMapping(value = "/editCampaign.do", method = { RequestMethod.POST })
public ModelAndView editCampaign(#RequestBody #Valid Campaign campaign,
HttpServletRequest request)
If a piece of required information is missing a MethodArgumentNotValidException is thrown (as I read in the docs). I really want to be able to return this information back to the view so I can show the user that they've not filled out a required field. Once the exception is thrown, it seems as though it's too late. Obviously, I don't have a bindingresult to inspect.
Am I incorrectly using the framework? How do I set up this scenario correctly?
First Of all I recommend you to return String instead ModelAndView especially in Spring 3.1.
If you want to catch Exception from #ResponseBody annotated method, I recommend you to use the following:
1) Catch the exception with #ExceptionHandler annotation:
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleMyException(MethodArgumentNotValidException exception) {
return "redirect:errorMessage?error="+exception.getMessage());
}
and then Redirect to view annotated with #ResponseBody argument
#RequestMapping(value="/errorMessage", method=RequestMethod.GET)
#Responsebody
public String handleMyExceptionOnRedirect(#RequestParamter("error") String error) {
return error;
}
I've been Googling my butt off trying to find out how to do this: I have a Jersey REST service. The request that invokes the REST service contains a JSON object. My question is, from the Jersey POST method implementation, how can I get access to the JSON that is in the body of the HTTP request?
Any tips, tricks, pointers to sample code would be greatly appreciated.
Thanks...
--Steve
As already suggested, changing the #Consumes Content-Type to text/plain will work, but it doesn't seem right from an REST API point of view.
Imagine your customer having to POST JSON to your API but needing to specify the Content-Type header as text/plain. It's not clean in my opinion. In simple terms, if your API accepts JSON then the request header should specify Content-Type: application/json.
In order to accept JSON but serialize it into a String object rather than a POJO you can implement a custom MessageBodyReader. Doing it this way is just as easy, and you won't have to compromise on your API spec.
It's worth reading the docs for MessageBodyReader so you know exactly how it works. This is how I did it:
Step 1. Implement a custom MessageBodyReader
#Provider
#Consumes("application/json")
public class CustomJsonReader<T> implements MessageBodyReader<T> {
#Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations,MediaType mediaType) {
return true;
}
#Override
public T readFrom(Class<T> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException {
/* Copy the input stream to String. Do this however you like.
* Here I use Commons IOUtils.
*/
StringWriter writer = new StringWriter();
IOUtils.copy(entityStream, writer, "UTF-8");
String json = writer.toString();
/* if the input stream is expected to be deserialized into a String,
* then just cast it
*/
if (String.class == genericType)
return type.cast(json);
/* Otherwise, deserialize the JSON into a POJO type.
* You can use whatever JSON library you want, here's
* a simply example using GSON.
*/
return new Gson().fromJson(json, genericType);
}
}
The basic concept above is to check if the input stream is expected to be converted to a String (specified by Type genericType). If so, then simply cast the JSON into the specified type (which will be a String). If the expected type is some sort of POJO, then use a JSON library (e.g. Jackson or GSON) to deserialize it to a POJO.
Step 2. Bind your MessageBodyReader
This depends on what framework you're using. I find that Guice and Jersey work well together. Here's how I bind my MessageBodyReader in Guice:
In my JerseyServletModule I bind the reader like so --
bind(CustomJsonReader.class).in(Scopes.SINGLETON);
The above CustomJsonReader will deserialize JSON payloads into POJOs as well as, if you simply want the raw JSON, String objects.
The benefit of doing it this way is that it will accept Content-Type: application/json. In other words, your request handler can be set to consume JSON, which seems proper:
#POST
#Path("/stuff")
#Consumes("application/json")
public void doStuff(String json) {
/* do stuff with the json string */
return;
}
Jersey supports low-level access to the parsed JSONObject using the Jettison types JSONObject and JSONArray.
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.3.8</version>
</dependency>
For example:
{
"A": "a value",
"B": "another value"
}
#POST
#Path("/")
#Consumes(MediaType.APPLICATION_JSON)
public void doStuff(JSONObject json) {
/* extract data values using DOM-like API */
String a = json.optString("A");
Strong b = json.optString("B");
return;
}
See the Jersey documentation for more examples.
I'm not sure how you would get at the JSON string itself, but you can certainly get at the data it contains as follows:
Define a JAXB annotated Java class (C) that has the same structure as the JSON object that is being passed on the request.
e.g. for a JSON message:
{
"A": "a value",
"B": "another value"
}
Use something like:
#XmlAccessorType(XmlAccessType.FIELD)
public class C
{
public String A;
public String B;
}
Then, you can define a method in your resource class with a parameter of type C. When Jersey invokes your method, the JAXB object will be created based on the POSTed JSON object.
#Path("/resource")
public class MyResource
{
#POST
public put(C c)
{
doSomething(c.A);
doSomethingElse(c.B);
}
}
This gives you access to the raw post.
#POST
#Path("/")
#Consumes("text/plain")
#Produces(MediaType.APPLICATION_JSON)
public String processRequset(String pData) {
// do some stuff,
return someJson;
}
Submit/POST the form/HTTP.POST with a parameter with the JSON as the value.
#QueryParam jsonString
public desolveJson(jsonString)
Some of the answers say a service function must use consumes=text/plain but my Jersey version is fine with application/json type. Jackson and Jersey version is
jackson-core=2.6.1, jersey-common=2.21.0.
#POST
#Path("/{name}/update/{code}")
#Consumes({ "application/json;charset=UTF-8" })
#Produces({ "application/json;charset=UTF-8" })
public Response doUpdate(#Context HttpServletRequest req, #PathParam("name") String name,
#PathParam("code") String code, String reqBody) {
System.out.println(reqBody);
StreamingOutput stream = new StreamingOutput() {
#Override public void write(OutputStream os) throws IOException, WebApplicationException {
..my fanzy custom json stream writer..
}
};
CacheControl cc = new CacheControl();
cc.setNoCache(true);
return Response.ok().type("application/json;charset=UTF-8")
.cacheControl(cc).entity(stream).build();
}
Client submits application/json request with a json request body. Servlet code may parse string to JSON object or save as-is to a database.
SIMPLE SOLUTION:
If you just have a simple JSON object coming to the server and you DON'T want to create a new POJO (java class) then just do this.
The JSON I am sending to the server
{
"studentId" : 1
}
The server code:
//just to show you the full name of JsonObject class
import javax.json.JsonObject;
#Path("/")
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response deleteStudent(JsonObject json) {
//Get studentId from body <-------- The relevant part
int studentId = json.getInt("studentId");
//Return something if necessery
return Response.ok().build();
}