Set spring jackson beanpropertyfilters in controller - java

I'm trying to serialize a hierarchy of objects using jackson, but I must use different fields in each controller method. Currently, I do this:
#RequestMapping(value = "/chat/message/create",
headers = "Accept=application/json",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET
)
#Transactional
public #ResponseBody
ChatMessage createChatMessage(#RequestParam("chat_id") Integer chatId,
#RequestParam("chat_from_id") Integer chatFromId,
#RequestParam("chat_content") String content) {
User fromUser = userDAO.find(User.class, chatFromId);
ChatMessage message = new ChatMessage();
message.setContent(content);
message.setLastUpdateDate(new Date());
message.setFromUser(fromUser);
Chat chat = chatDAO.find(Chat.class, chatId);
message.setChat(chat);
chatMessageDAO.save(message);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
FilterProvider filters = new SimpleFilterProvider()
.addFilter("chatMessageFilter", SimpleBeanPropertyFilter.serializeAllExcept(Collections.EMPTY_SET));
ObjectWriter writer = mapper.writer(filters);
try {
return writer.writeValueAsString(message);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
The problem is I want to use MappingJackson2HttpMessageConverter, but I cannot find a way to set the filter provider of list of beanpropertyfilters on it. I WANT to do something like this:
#RequestMapping(value = "/chat/message/create",
headers = "Accept=application/json",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET
)
#Transactional
public #ResponseBody
ChatMessage createChatMessage(#RequestParam("chat_id") Integer chatId,
#RequestParam("chat_from_id") Integer chatFromId,
#RequestParam("chat_content") String content) {
User fromUser = userDAO.find(User.class, chatFromId);
ChatMessage message = new ChatMessage();
message.setContent(content);
message.setLastUpdateDate(new Date());
message.setFromUser(fromUser);
Chat chat = chatDAO.find(Chat.class, chatId);
message.setChat(chat);
chatMessageDAO.save(message);
getFilterProvider().addFilter("chatMessageFilter", SimpleBeanPropertyFilter.serializeAllExcept(Collections.EMPTY_SET));
return message;
}
The getFilterProvider() would get the FilterProvider that is used by MappingJackson2HttpMessageConverter. That way, I would set 5 or 6 filters in one method, and a completely different set of filters in another method.
Note: Json views will not help me here. They are way too static, and I can't combine to specify which views to use for nested objects.

Related

Passing a BODY of a POST request to the server

I have built a Restful-API Java(SpringBoot) and created the needed requests.
The following request is a POST Request to add new Category.
I have tested the POST request by POSTMAN, and it working as expected.
I am building the client-side in ASP.NET 5.x.x.
Now the problem appear when I am calling the post request, it seems the API doesn't receive the data (#RequestBody category) that has been send from the client.
Here is a code simple of how I have created them
Server Side:
#ResponseStatus(HttpStatus.CREATED)
#PostMapping(value = "/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public CategoryDTO create(#RequestBody CategoryDTO category) {
log.info("Adding new Category Name: " + category.getName());
return categoryMapper.asCategoryDTO(categoryService.save(categoryMapper.asCategory(category)));
}
Client Side
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Category category)
{
Category newCategory = new Category();
// Serialize the concrete class into a JSON String
var stringPayload = JsonConvert.SerializeObject(category);
// Wrap the JSON inside a StringContent which then can be used by the HttpClient class
StringContent content = new StringContent(stringPayload);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.PostAsync("http://localhost:8080/category/add", content))
{
string apiResponse = await response.Content.ReadAsStringAsync();
newCategory = JsonConvert.DeserializeObject<Category>(apiResponse);
}
}
return RedirectToAction("Index");
}
I don't know what is wrong there, could anybody help!
EDIT--
Here is the request via postman
EDIT
I have created another POST request but as a RequestParam instead of RequestBody
#ResponseStatus(HttpStatus.CREATED)
#PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
public CategoryDTO addCategory(#RequestParam(name = "categoryName") String categoryName){
return categoryMapper.asCategoryDTO(categoryService.addCategory(categoryName));
}
and created in the client side the request as following
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Category category)
{
Category newCategory = new Category();
var parameters = new Dictionary<string, string> { { "categoryName", category.Name } };
var encodedContent = new FormUrlEncodedContent(parameters);
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.PostAsync("http://localhost:8080/category/add", encodedContent))
{
string apiResponse = await response.Content.ReadAsStringAsync();
newCategory = JsonConvert.DeserializeObject<Category>(apiResponse);
}
}
return RedirectToAction("Index");
}
And It's works fine!
So the problem is how to pass the data via the httpClient, which need to be of type RequestBody (the data in the body not in the header!) also as a application/json.
So how to pass the data?
I suppose that your spring boot application just blocks POST request because you didn't provide instruction how to handle requests. Try to disable csrf protection like it did here: https://stackoverflow.com/a/48935484/13314717
It might be a problem in the naming convention. Starting with capital letters in properties in C#, and starting with lowercase in Java.
If your class looks like this in C#
class Category {
int Id;
string Name;
...
}
And like this in Java
class Category {
int id;
string name;
...
}
It is not going to work so you have to match the property names. So make either both id or both Id.

Retrieve value from returned JSON object in Java

I am a receiving a JSON object and I need to save the values to my DB. But I'm having an issue figuring out how to retrieve the particular values in the JSON object.
In this case, I want to retrieve the values of 'originationNumber' and 'messageBody'
The response object -
{"originationNumber":"***","destinationNumber":"***","messageKeyword":"KEYWORD_***","messageBody":"Answer ","previousPublishedMessageId":"1slamq6mdpucd8q4i7iabf1sikc629ga253tr6o0","inboundMessageId":"88bc02fc-aff3-4277-ac1d-f27b6d3b6abb"}
Method to receive message -
public String getReceivedMessages(Messaging receivedMessage) {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);
AmazonSQS sqsClient = AmazonSQSClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion(String.valueOf(awsRegion)).build();
StringBuilder sb = new StringBuilder();
String queueUrl = "https://sqs.us-east-1.amazonaws.com/1234567/GetReceivedMessages";
List<Message> messages = sqsClient.receiveMessage(new ReceiveMessageRequest(queueUrl)
.withMaxNumberOfMessages(1).withWaitTimeSeconds(20)).getMessages();
for (Message message : messages) {
sb.append(message.getBody());
sqsClient.deleteMessage(queueUrl, message.getReceiptHandle());
}
// Save messages to DB
String userId = connectionRequestRepository.getUserId();
Date date = new Date();
Timestamp now = new Timestamp(date.getTime());
receivedMessage.setUserId(userId);
receivedMessage.setOriginationNumber("");
receivedMessage.setDestinationNumber("***");
receivedMessage.setMessageBody("");
receivedMessage.setMessageType("RECEIVED");
receivedMessage.setCreatedAt(now);
messagingRepository.save(receivedMessage);
System.out.println(sb); <--- Prints response object to console
return sb.toString();
}
You can use jackson library for that.
Solution 1: You can use ObjectMapper as below:
Message Class to map JSON to Java Object:
public class Message {
private String originationNumber;
private String messageBody;
// public getter and setters methods
}
Create Object From JSON String:
Message message = null;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
message = objectMapper.readValue(payload, Message.class);
} catch (JsonProcessingException e) {
// Log Or do some action as per need
}
Here message will have those values. DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES is used here to skip exception as there are other fields also in JSON and not present in Java Class (as we don't need them).
Solution 2: Alternatively you can use JsonNode from same Jackson library and read nodes one by one as below:
ObjectMapper mapper = new ObjectMapper();
JsonNode actualObj = mapper.readTree("{\"originationNumber\":\"***\",\"destinationNumber\":\"***\",\"messageKeyword\":\"KEYWORD_***\",\"messageBody\":\"Answer \",\"previousPublishedMessageId\":\"1slamq6mdpucd8q4i7iabf1sikc629ga253tr6o0\",\"inboundMessageId\":\"88bc02fc-aff3-4277-ac1d-f27b6d3b6abb\"}");
String originationNumber = actualObj.get("originationNumber");
String messageBody = actualObj.get("messageBody");
In this approach you won't need to create Message class.
You can convert the json string into a json object
https://www.javatpoint.com/how-to-convert-string-to-json-object-in-java
String string = "{\"originationNumber\":\"***\",\"destinationNumber\":\"***\",\"messageKeyword\":\"KEYWORD_***\",\"messageBody\":\"Answer \",\"previousPublishedMessageId\":\"1slamq6mdpucd8q4i7iabf1sikc629ga253tr6o0\",\"inboundMessageId\":\"88bc02fc-aff3-4277-ac1d-f27b6d3b6abb\"}";
JSONObject json = new JSONObject(string);
System.out.println(json.toString());
String destinationNumber = json.getString("destinationNumber");
System.out.println(destinationNumber);
where ur json like
{"originationNumber":"***",
"destinationNumber":"***",
"messageKeyword":"KEYWORD_***",
"messageBody":"Answer","previousPublishedMessageId":"1slamq6mdpucd8q4i7iabf1sikc629ga253tr6o0",
"inboundMessageId":"88bc02fc-aff3-4277-ac1d-f27b6d3b6abb"
}
it like
"key":Value
i think ur code will be like
receivedMessage.setUserId(userId);
receivedMessage.setOriginationNumber("originationNumber");
receivedMessage.setDestinationNumber("destinationNumber");
receivedMessage.setMessageBody("messageBody");
receivedMessage.setMessageType("RECEIVED");
receivedMessage.setCreatedAt(now);
messagingRepository.save(receivedMessage);

How to get raw JSON body in Spring REST controller?

The API below accept a json string from client, and the map it into a Email object. How can I get request body (email) as a raw String? (I want both raw-string and typed version of email parameter)
PS: This question is NOT a duplicate of: How to access plain json body in Spring rest controller?
#PostMapping(value = "/mailsender")
public ResponseEntity<Void> sendMail(#RequestBody Email email) {
//...
return new ResponseEntity<>(HttpStatus.OK);
}
You can do it in more than one way, listing two
1. **Taking string as the paramater**,
#PostMapping(value = "/mailsender")
public ResponseEntity<Void> sendMail(#RequestBody String email) {
//... the email is the string can be converted to Json using new JSONObject(email) or using jackson.
return new ResponseEntity<>(HttpStatus.OK);
}
2. **Using Jackson**
#PostMapping(value = "/mailsender")
public ResponseEntity<Void> sendMail(#RequestBody Email email) {
//...
ObjectMapper mapper = new ObjectMapper();
String email = mapper.writeValueAsString(email); //this is in string now
return new ResponseEntity<>(HttpStatus.OK);
}
Spring uses Jackson for this in the back, you could use it to serialize it to an string. Like so:
#Autowired private ObjectMapper jacksonMapper;
#PostMapping(value = "/mailsender")
public ResponseEntity<Void> sendMail(#RequestBody Email email) {
//...
log.info("Object as String: " + jacksonMapper.writeValueAsString(email));
return new ResponseEntity<>(HttpStatus.OK);
}
you can create json of type string using GSON library
Gson gson = new Gson();
#PostMapping(value = "/endpoint")
public ResponseEntity<Void> actionController(#RequestBody Car car) {
//...
log.info("Object as String: " + this.gson.toJson(car));
return new ResponseEntity<>(HttpStatus.OK);
}
I did not get all things about this question, but I try to answer as I understand. Well,
if you want to get request body:
as you say How to access plain json body in Spring rest controller? here already writen how to do this. If something wrong, maybe you send wrong json or not suitable type as you wite inside Email class. Maybe your request comes url filter
second way try like this:
private final ObjectMapper mapper = new ObjectMapper();
#PostMapping(value = "/mailsender")
public ResponseEntity<Void> sendMail(HttpServletRequest req) {
// read request body
InputStream body = req.getInputStream();
byte[] result = ByteStreams.toByteArray(body);
String text =new String(result,"UTF-8");
//convert to object
Email email = mapper.readValue(body, Email .class);
return new ResponseEntity<>(HttpStatus.OK);
}
If you want to convert object to json string read this post

Spring RestTemplate: How to serialize java.util.Collection

I need to call a RESTful webservice from a Java program and pass it to a DateTime Collection.
So, my code is like the following:
RestTemplate restTemplate = new RestTemplate();
try {
String scheme = request.getScheme();
String userInfo = request.getRemoteUser();
String host = request.getLocalAddr();
int port = request.getLocalPort();
String path = "/myapp/common/myapi";
MultiValueMap<String, Object> requestParams = new LinkedMultiValueMap<String, Object>();
requestParams.add("aId", objA.getId());
requestParams.add("bIb", objB.getId());
for (DateTime date : dates) {
requestParams.add("dates", date);
}
URI apiUri = new URI(scheme, userInfo, host, port, path, null, null);
result = restTemplate.postForObject(apiUri.toString(), request, BigDecimal.class,
requestParams);
} catch (URISyntaxException e) {
logger.error(e.getMessage());
} catch (DataAccessException e) {
logger.error(e.getMessage());
}
And the webservice's signature is like:
#RequestMapping(value = "myapi", method = RequestMethod.POST)
public #ResponseBody BigDecimal myApi(
#RequestParam("dates") final List<DateTime> dates,
#RequestParam("aId") final Integer aId, #RequestParam("bId") final Integer bId) {
[...]
return result;
}
But I get the error:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: No serializer found for class java.util.Collections$3 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
I understand that the problem is the serialization of the collection, but I do not know how to solve it.
Updated
I also tried with a Command class, so something like:
public class CalcolaGiorniResiduiCommand implements Serializable {
private List<DateTime> dates;
//[...]
}
Changed the controller:
#RequestMapping(value = "myapi", method = RequestMethod.POST)
public #ResponseBody BigDecimal myApi(
#RequestParam("command") final MyCommand command) {
And finally:
MyCommand command = MyCommand.build(1, 1, dates); //dates is a DateTime Collection
restTemplate.postForObject(apiUri.toString(), request, BigDecimal.class, command);
But I get the same error.
Any help, please?
I think you have to pass a list of dateTime object rather than adding it multiple time. Can you please try with the below snippet.
List<DateTime> dates = new ArrayList<>();
for (DateTime date : dates) {
dates.add(date);
}
requestParams.add("dates", dates);
From the documentation, the request parameter is the Object to be POSTed, so the command object have to be passed as second parameter instead of the object request.
So the code should be changed from:
restTemplate.postForObject(apiUri.toString(), request, BigDecimal.class, command);
to:
restTemplate.postForObject(apiUri.toString(), command, BigDecimal.class);

How to make Content-Type header optional?

I've an heartbeat API implemeted using Spring REST service:
#RequestMapping(value = "heartbeat", method = RequestMethod.GET, consumes="application/json")
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}
And MyService.java has below method:
public String getCurrentTime() throws Exception {
String currentDateTime = null;
MyJson json = new MyJson();
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
try {
Date currDate = new Date(System.currentTimeMillis());
currentDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(currDate);
json.setTime(currentDateTime);
ObjectWriter writer = mapper.writerWithView(Views.HeartBeatApi.class);
return writer.writeValueAsString(json);
} catch (Exception e) {
throw new Exception("Excpetion", HttpStatus.BAD_REQUEST);
}
}
It works as expected but have 2 issues:
When I invoke this API, Content-Type header is mandatory & I want to know how to make this header optional.
How to add "Accept" header so that it can support other format such as Google Protobuf?
Thanks!
If you don't want to require Content-Type exist and be "application/json", you can just omit the consumes section entirely.
"Accept" is available via the "produces" value, as opposed to "consumes." So if you wanted to support Google Protobuf OR application/json, you could do this:
#Controller
#RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
#ResponseBody
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}

Categories

Resources