In my spring-mvc application, the client requests with a key and I lookup the associated value in Couchbase. From Couchbase I get a json object as a String, as my application doesn't need to do anything with it I just want this to be written out.
But Spring sees that I want to write a String, and passes it to jackson which then writes it as a json string - adding in quotation marks and escaping the internals.
Simplest example:
#RequestMapping(value = "/thing", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public DeferredResult<String> getThing() {
final DeferredResult<String> result = new DeferredResult<>();
// in future
result.setResult("{}");
return result;
}
Returns: "{}"
I'm thinking of making a serialised json wrapper and a custom serialiser for jackson to just output it's contents.
I think you could use org.springframework.http.ResponseEntity
#RequestMapping(value = "/example", headers = "Accept=application/json")
#ResponseBody
#Transactional
public ResponseEntity<String> example(#RequestParam(value = "documentId", required = true) Integer documentId) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
SomeDTO result = SomeDTO.findDocument(documentId);
if (result == null) {
return new ResponseEntity<String>(headers, HttpStatus.NOT_FOUND);
}
return new ResponseEntity<String>(result.toJson(), headers,HttpStatus.OK);
}
beside, you could use Flexjson to tranfer Oject to Json String.
I added the StringHttpMessageConverter with application/json as a supportedMediaType:
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value = "application/json" />
</bean>
</mvc:message-converters>
This did get the String responses output as-is.
Related
I'm writing a unit test for a controller method that accepts a MultipartFile
and a custom object MessageAttachment. So far I can see that the MultipartFile is the correct format for the request but the MessageAttachment is not.
The parsing of the messageAttachment throws a server side 500 error with MethodArgumentConversionNotSupportedException.
It seem to be an issue with converting the MessageAttachment to a MockMultipartFile in the test. This is similar to the example shown here - https://stackoverflow.com/a/21805186
Question:
How can you resolve a MethodArgumentConversionNotSupportedException with MockMvc?
Controller method under test
#RequestMapping(value = "/", method = RequestMethod.POST, consumes = "multipart/form-data", produces = "application/json")
public ResponseEntity<MessageAttachment> handleFileUpload(#RequestParam(value = "file", required = true) MultipartFile file, #RequestParam(value = "messageAttachment") MessageAttachment messageAttachment) {
//do stuff with the file and attachment passed in..
MessageAttachment attachment = new MessageAttachment();
return ResponseEntity.accepted().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getOriginalFilename() + "\"").body(attachment);
}
MockMvc Test
#Test
public void shouldSaveUploadedFile() throws Exception {
// Given
ObjectMapper mapper = new ObjectMapper();
MessageAttachment messageAttachment = new MessageAttachment();
messageAttachment.setTimestamp(new Date());
MockMultipartFile multipartFile = new MockMultipartFile("file", "test.txt", "text/plain",
"Spring Framework".getBytes());
//Mapping the msgAttachment to a MockMultipartFile HERE
MockMultipartFile msgAttachment = new MockMultipartFile("messageAttachment", "","application/json",
mapper.writeValueAsString(messageAttachment).getBytes());
// When
this.mockMvc.perform(MockMvcRequestBuilders.multipart("/media/")
.file(multipartFile)
.file(msgAttachment)).andDo(MockMvcResultHandlers.print());
}
Console output of MockMvcResultHandlers.print()
MockHttpServletRequest:
HTTP Method = POST
Request URI = /media/
Parameters = {}
Headers = {Content-Type=[multipart/form-data]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.fizz.buzz.fizzapi.controller.MediaUploadController
Method = public org.springframework.http.ResponseEntity<com.fizz.buzz.fizzapi.model.MessageAttachment> com.fizz.buzz.fizzapi.controller.MediaUploadController.handleFileUpload(org.springframework.web.multipart.Mu
ltipartFile,com.fizz.buzz.fizzapi.model.MessageAttachment,javax.servlet.http.HttpServletRequest)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
You'll want to use #RequestPart instead of #RequestParam for the part of the request that is application/json. The javadoc for #RequestPart states
Supported method argument types include MultipartFile in conjunction
with Spring's MultipartResolver abstraction,
javax.servlet.http.Part in conjunction with Servlet 3.0 multipart
requests, or otherwise for any other method argument, the content of
the part is passed through an HttpMessageConverter taking into
consideration the 'Content-Type' header of the request part. This is
analogous to what #RequestBody does to resolve an argument based on
the content of a non-multipart regular request.
Note that #RequestParam annotation can also be used to associate the
part of a "multipart/form-data" request with a method argument
supporting the same method argument types. The main difference is that
when the method argument is not a String, #RequestParam relies on type
conversion via a registered Converter or PropertyEditor while
#RequestPart relies on HttpMessageConverters taking into consideration
the 'Content-Type' header of the request part. #RequestParam is likely
to be used with name-value form fields while #RequestPart is likely to
be used with parts containing more complex content (e.g. JSON, XML).
Presumably, you haven't registered a Converter, nor a PropertyEditor, to parse the content of that part, whereas an HttpMessageConverter for JSON is automatically registered (depending on your Spring MVC/Boot version) if you have Jackson on the classpath.
I meet a request to upload files with spring resttemplate to upload files
with http header "multipart/form-data", also some other normal parameters need to be posted. how to implements that?
you can use the following code in your application to have both multipartfile and normal request parameters at the same time.
Replace the url with your own.
replace param and value according to your normal parameters.
String url ="http://example.com";
String fileAbsPath ="absolute path of your file";
String fileName = new File(fileAbsPath).getName();
Files.readAllBytes(Paths.get(fileAbsPath));
MultiValueMap<String, Object> data = new LinkedMultiValueMap<String, Object>();
ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(Paths.get(fileAbsPath))) {
#Override
public String getFilename() {
return fileName;
}
};
data.add("file", resource);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("file","application/pdf");
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("param1", "value1")
.queryParam("param2", "value2")
HttpEntity<> entity =
new HttpEntity<> (data, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> result =restTemplate.exchange(
builder.toUriString(),
HttpMethod.POST,
entity,
String.class);
System.out.println(result.getBody());
you can use this code.
HttpHeaders headers = getCASHeaders(MediaType.MULTIPART_FORM_DATA);
LinkedMultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("fileField", new FileSystemResource(""));//get file resource
params.add("stringfield", stringPayload);
HttpEntity requestEntity = new HttpEntity<>(params, headers);
ResponseEntity<CasAssetApiResponse> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
This will send post call with two param, you can add more according to your wish.
Please have a look at this stackoverflow answer as well
I got the error "cannot be cast to java.lang.String" although my code does not have any casting.
Update: See here for solution
Using Spring MVC 4.
Here's my JavaScript code that makes the POST request:
$("input.toggleCourse").change(function(e){
var d = {
classID: classID,
courseID: courseID
};
$.post(
"<c:url value="/class/addCourse" />",
JSON.stringify(d))
.done(function(data){
alert("ok");
});
});
(Tried with and without JSON.stringify, tried full $.ajax instead of $.post)
Here's my controller
#RequestMapping(value = "/class/addCourse", method = RequestMethod.POST)
public #ResponseBody String addCourse(#RequestBody final CourseInClass cic) {
StringBuilder sb = new StringBuilder();
try{
Class c = classServ.findOne(cic.ClassID);
c.Courses.add(courseServ.findOne(cic.CourseID));
sb.append("{success:true}");
} catch (Exception e){
sb.append("{error:\"").append(e.getMessage()).append("\"}");
}
return sb.toString();
}
I checked the network log that it sends the correct headers to the correct url. Post requests work for normal forms, but not for this ajax call.
Thanks.
How do you think (String classID, String courseID) will be detected by Spring. i.e. how will the json object be mapped to java object.
If you want to use auto binding you can use jackson-mapper-asl. Take a look at this page
If you don't want to use it you can use #PathVariable,
change method signatures to public #ResponseBody String addCourse(#PathVariable String classID, #PathVariable String courseID) {..}
and then hit http://localhost:8080/<appname>/class/addCourse/<classID>/<courseID>
I have this code in service
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string GetJson(int nNumResults)
System.Web.Script.Serialization.JavaScriptSerializer serializer = new
System.Web.Script.Serialization.JavaScriptSerializer();
List<Dictionary<string, object>> rows =
new List<Dictionary<string, object>>();
Dictionary<string, object> row = null;
// load dataset from sql query (source not posted here)
DataSet dset = new DataSet();
dadapter.Fill(dset);
if (dset.Tables[0].Rows.Count < 1)
{
conn1.Close();
return null;
}
conn1.Close();
foreach (DataRow dr in dset.Tables[0].Rows)
{
row = new Dictionary<string, object>();
foreach (DataColumn col in dset.Tables[0].Columns)
{
row.Add(col.ColumnName.Trim(), dr[col]);
}
rows.Add(row);
}
return serializer.Serialize(rows);
}
Everything is ok, except that the returned string is json with
<string xmlns="http://www.mysite.com/service">
and
</string>
at the end. If I remove these tags the json can be parsed without any problems.
How can I get the json string without the xml tag at the beginning and end?
I use this request from Android which does not read a valid JSON:
Map<String, Object> params = new HashMap<String, Object>();
params.put("nNumQuotes", "100");
aq.ajax(url, params, JSONObject.class, new AjaxCallback<JSONObject>() {
#Override
public void callback(String url, JSONObject json, AjaxStatus status) {
try {
String aJsonString = json.getString("Id");
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
And also tested from browser with the integrated service test and the same result.
Are you including the content-type header in your request?
contentType: "application/json; charset=utf-8"
You should add "accept: application/json;" to your header to make sure the server knows you want your data back as JSON, as long as your web service can actually return data in JSON format.
When you test the REST web service in your browser (at least in chrome), it sets the 'accept-type' in the header to this:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
By testing using something like Fiddler, you can change this to something like:
Accept: application/json
That can tell a web service to return data in JSON format. The android code you are using may not be specifying that it wants JSON data. Again, you can use fiddler to see what the android code request looks like.
I just came across this post about a similar issue: ASP.NET JSON web service always return the JSON response wrapped in XML
I have a REST API that until now always returned JSONP (JSON data wrapped in whatever function call client wanted):
static final String JAVASCRIPT = "application/javascript;charset=UTF-8";
#RequestMapping(value = "/matches", produces = JAVASCRIPT)
#ResponseBody
public String matches(#RequestParam String callback) {
String json = jsonService.getCachedJson("/matches");
return toJsonp(callback, json);
}
Now, things have changed so that I need to return either JSON or JSONP: if client provides a callback function name, we return JSONP and otherwise pure JSON.
With regards to content type, I'd like to be as correct as possible and use application/json for JSON and application/javascript for JSONP.
So, something like this:
#RequestMapping(value = "/matches")
#ResponseBody
public String matches(#RequestParam(required = false) String callback) {
String json = jsonService.getCachedJson("/matches");
// TODO: if callback == null, set content type to "application/json",
// otherwise to "application/javascript"
return jsonOrJsonp(callback, json);
}
String jsonOrJsonp(String callback, String json) {
return Strings.isNullOrEmpty(callback) ? json : toJsonP(callback, json);
}
Looks like I can no longer use produces attribute of #RequestMapping. What's the simplest way to set content type with Spring MVC in the scenario above?
I'd like to avoid defining HttpMessageConverters (or other Spring hassle) or changing the method return type, if at all possible! And obviously I wouldn't like duplicated method declarations where produces value is the only significant difference. What I'm looking for is minimal changes to the above code.
Latest Spring (3.2.3).
Have you tried just using two request handler methods?
#RequestMapping(value = "/matches", produces = JAVASCRIPT, params="callback")
#ResponseBody
public String Jsonp(#RequestParam String callback) {
return toJsonp(callback, jsonService.getCachedJson("/matches"));
}
#RequestMapping(value = "/matches", produces = JSON)
#ResponseBody
public String json() {
return toJson(jsonService.getCachedJson("/matches"));
}
The first method with the params parameter will only be mapped to requests where the callback param is present.