Get both Mapping and Pain Json text in Spring Boot Restful webService - java

i am trying to parse json body request coming in for a post request using Spring Boot. I would like to map the body to fields on vehicle class and also to store plain json body to some variable as well for future use. But i am always getting stream closed exception when trying to access plain json body. Can someone help me out on this. Thanks In Advance
Code
#RequestMapping(value = "/GetDriverDetails", method = RequestMethod.POST)
public ResponseEntity<Vehicle> GetVehicleDetails(#RequestBody Vehicle vehicle, HttpServletRequest request) {
System.out.println(vehicle);
String json;
if ("POST".equalsIgnoreCase(request.getMethod()))
{
try {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return new ResponseEntity<Vehicle>(HttpStatus.OK);
}
Json request Body
{
"vehicleName": "Brio",
"vehicleModel": "fisrtClass",
"drivers": [
{
"name": "rej",
"licenseNumber": "KLLicense1"
},
{
"name": "Dan",
"licenseNumber": "KLLicense2"
},
{
"name": "bala",
"licenseNumber": "KLLicense3"
},
{
"name": "vijay",
"licenseNumber": "KLLicense4"
},
{
"name": "aravind",
"licenseNumber": "KLLicense5"
},
{
"name": "sathya",
"licenseNumber": "KLLicense6"
}
]
}
Exception
java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.springframework.web.util.ContentCachingRequestWrapper$ContentCachingInputStream.read(ContentCachingRequestWrapper.java:254) ~[spring-web-5.2.9.RELEASE.jar:5.2.9.RELEASE]
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:297) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188) ~[na:na]
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:181) ~[na:na]

Can you try following code:
The solution to your main problem, since you are using #RequestBody, contents are already read and mapped to pojo class hence stream is utlized and closed in this case you do not want to use #RequestBody at all. Please find my implementation below:
#PostMapping(path = "update-vehicle-details", consumes = MediaType.ALL_VALUE)
public VehicleDriver updateVehicleDetails(HttpServletRequest request) throws IOException {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
final VehicleDriver vehicleDriver = new ObjectMapper().readValue(collect, VehicleDriver.class);
return vehicleDriver;
}
Otherwise, use a simple approach, read the value from application json content type parses in requestbody and converts that body to string and return the same result
#RestController
public static class TestController {
#PostMapping(path = "update-vehicle-details", consumes = MediaType.APPLICATION_JSON_VALUE)
public String updateVehicleDetails(#RequestBody VehicleDriver vehicleDriver) throws JsonProcessingException {
final StringBuilder stringBuilder = new StringBuilder(vehicleDriver.vehicleName);
List<String> driverDetails = Optional.ofNullable(
vehicleDriver.drivers)
.map(Collection::stream)
.orElse(Stream.empty())
.map(d -> "name=: " + d.name + ", license number:" + d.licenseNumber)
.collect(Collectors.toList());
stringBuilder.append("\n");
stringBuilder.append(driverDetails);
String stringRepresentationOfBody = new ObjectMapper().writeValueAsString(vehicleDriver);
// return stringBuilder.toString();
return stringRepresentationOfBody;
}
}
public static class VehicleDriver {
public String vehicleName;
public String vehicleModel;
public List<Driver> drivers;
}
public static class Driver {
public String name;
public String licenseNumber;
}

Try using Object Mapper for converting your vehicle object to json string and in that case you would not be needing request in method argument.
And you are using post request method then if condition is not needed.

Related

Parse JSON with Spring WebFlux after error occured

I'm receiving JSON from REST API looks like:
{
"items": [
{
"id": 60659,
"name": "Display",
"active": true,
"account_id": 235
},
{
"id": 36397,
"name": " Mail Display",
"active": true,
"account_id": 107
}
]
}
I'm using this method to parse it:
Mono<List<Item>> getItems(String token) {
return webCLient
.get()
.headers(httpHeaders -> httpHeaders.setBearerAuth(token))
.retrieve()
.bodyToMono(ItemResponse.class)
.map(ItemResponse::getResponse)
.retryBackoff(RetrySettings.RETRIES, RetrySettings.FIRST_BACKOFF, RetrySettings.MAX_BACKOFF)
.doOnError(e -> log.error("error: " + e.getCause().toString()))
Response:
public class ItemResponse {
#JsonProperty("items")
private List<Item> response;
}
But sometimes 3rd party API returns different response without top level items property and looks like:
[
{
"id": 60659,
"name": "Display",
"active": true,
"account_id": 235
},
{
"id": 36397,
"name": " Mail Display",
"active": true,
"account_id": 107
}
]
At this point my app is crashing with JSON decoding error. I used for this case:
bodyToMono(new ParameterizedTypeReference<List<Item>>() {})
But I can't always refactoring this part of code just to handle their json. How to do it in dynamical way with Spring WebFlux? Like try -> parse#1 -> catch -> parse#2. So i need to parse json in way#1 and if error occurs app should try to parse it with way#2.
You can get the response as a string .bodyToMono(String.class) and do whatever you want, with multiple try catches... but I think your best bet is to create a custom Deserializer and use it with your WebClient via ExchangeStrategies like described here: How to customize SpringWebFlux WebClient JSON deserialization?
.
class MyResponse {
List<Object> data;
MyResponse(List<Object> data) {
this.data = data;
}
}
class MyResponseDeserializer extends JsonDeserializer<MyResponse> {
#Override
public MyResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);
List<Object> data = new ArrayList<>();
if (treeNode.isArray()) {
// parse it as array
} else {
// parse it as object and put inside list
}
MyResponse myResponse = new MyResponse(data);
return myResponse;
}
}
And then
WebClient getWebClient() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(MyResponse.class, new MyResponseDeserializer());
objectMapper.registerModule(simpleModule);
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON));
}).build();
return WebClient.builder().exchangeStrategies(strategies).build();
}
Mono<List<Item>> getItems(String token) {
return getWebClient()
.get()
.headers(httpHeaders -> httpHeaders.setBearerAuth(token))
.retrieve()
.bodyToMono(MyResponse.class)
.map(MyResponse::data)
.retryBackoff(RetrySettings.RETRIES, RetrySettings.FIRST_BACKOFF, RetrySettings.MAX_BACKOFF)
.doOnError(e -> log.error("error: " + e.getCause().toString()))
}
The rest is the same as in your example just change the class name and add appropriate fields.
And of course this is just a fast written demo and everything hardcoded and within a one method, better to have them injected

How to model JSON with key value pairs?

This simple JSON is returned by https://httpbin.org/get which is handy for testing.
{
"args": {},
"headers": {
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "AemSConnector v1.0",
"X-Amzn-Trace-Id": "Root=1-606c333f-338353e14fc31e375617f4ba"
},
"origin": "81.40.159.142",
"url": "https://httpbin.org/get"
}
I'm trying to figure out how to build a Java class to model this.
I have tried:
public class ModelTest {
public String origin;
public String url;
public HashMap<String, String> headers;
public HashMap<String, String> args;
// getters and setters and default constructor here...
}
}
And also just this:
public class ModelTest {
public String origin;
public String url;
// getters and setters and default constructor here...
}
}
But when I try to convert the JSON string to this model, I just get a null point exception in the logs, no helpful info.
The code I am using is this:
// ModelTest model = null;
ModelTest model = new ModelTest();
model = (ModelTest) getObjectFromJson(reply, model);
}
:
public static Object getObjectFromJson(String jsonString, Object obj) {
Gson gson = new Gson();
Object returnValue = null;
try {
returnValue = gson.fromJson(jsonString, obj.getClass());
} catch (Exception e) {
log.error("Exception occured in Something :: getObjectFromJson --> ", e);
}
return returnValue;
}
exception:
2021-04-06 12:09:04.245 ERROR [com.adobe.aem.guides.wknd.core.util.MyConnector] Exception occured in Something :: getObjectFromJson -->
java.lang.NullPointerException: null
at com.adobe.aem.guides.wknd.core.util.SpineConnector.getObjectFromJson(MyConnector.java:77) [aem-guides-wknd.core:0.2.1.SNAPSHOT]
at com.adobe.aem.guides.wknd.core.util.SpineConnector.get(MyConnector.java:50) [aem-guides-wknd.core:0.2.1.SNAPSHOT]
at com.adobe.aem.guides.wknd.core.servlets.SpineServlet.doGet(MyServlet.java:64) [aem-guides-wknd.core:0.2.1.SNAPSHOT]
I found a solution. The Model was fine, it was the getObjectFromJson method which was causing the issues, even with a non-null object (as the commenters pointed out)
I scrapped it, and did the mapping in-line and it worked as expected:
Gson gson = new Gson();
model = gson.fromJson(reply, ModelTest.class);

Why does the string returned as "4-5 u043Bu0435u0442 103-112 u0441u043C"?

I am developing backend which collect data from different sources (REST APIs) and return summarized data.
So I requesting data from one of APIs. Response from that API is (all Cyrillic characters looks good) described as #Data class ProductSku {private string label; private boolean isInStock;}:
[
{
//... omit lot of unused fields
"label": "1 год 73-75 см",
"isInStock": true
},
{
//... omit lot of unused fields
"label": "2 года 82-88 см",
"isInStock": true
}
]
Then I allow to request it among other datas from my backend endpoint:
#GetMapping(path = "products/{prodId}", produces = {"application/json; charset=UTF-8","*/*;charset=UTF-8"})
public ProductEntity getProduct(#PathVariable("prodId") int prodId) {
return catalogService.getProduct(prodId);
}
class CatalogService {
public ProductEntity getProduct(int prodId) {
// ...
// ommited fill up result from other APIs
List<ProductData> productSkus = stockClient.getProductSkus(prodId);
productEntity.addSkus(productSkus);
return productEntity;
}
}
class StockClient {
public List<ProductSku> getProductSkus(int prodId) {
// using restTemplate
// querying by param productId
ProductData[] productsData;
try {
var headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
headers.set(HttpHeaders.PRAGMA, "no-cache");
headers.set(HttpHeaders.CACHE_CONTROL, "no-cache");
var uri = UriComponentsBuilder.fromHttpUrl(apiUrl).queryParam("productId", prodId).build().toUri();
var requestEntity = RequestEntity.get(uri).headers(headers).build();
productsData = restTemplate.exchange(requestEntity, ProductData[].class).getBody();
} catch (...) {
//...
}
// convert to list here
return new Arrays.asList(productData);
}
}
// ...
So it respond me as:
{
// ...
"skus": [
{
"size": "1 u0433u043Eu0434 73-75 u0441u043C",
"inStock": true
},
{
"size": "2 u0433u043Eu0434u0430 82-88 u0441u043C",
"inStock": true
}
]
// ...
}
As you can see instead of "2 года 82-88 см" i get "2 u0433u043Eu0434u0430 82-88 u0441u043C"
Cyrillic symbols from other APIs appears normally.
I am hope for your help

Spring ResponsEntity body contains extra json with timestamp, status and more

We have an REST endpoint that will add a new empty ingredient to an existing meal:
#RequestMapping(value = "/add", method = RequestMethod.PUT, consumes = "application/json;charset=UTF-8")
public ResponseEntity<Object> add(#RequestBody final Meal meal) throws URISyntaxException
{
Optional<Meal> optionalMeal = mealRepository.findById(meal.getId());
if (!optionalMeal.isPresent()) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(MessageUtil.parse(MSG_404_MEAL, meal.getId() + ""));
}
Ingredient ingredient = new Ingredient();
ingredient.setMeal(optionalMeal.get());
ingredientRepository.saveAndFlush(ingredient);
ResponseEntity re = ResponseEntity
.created(RequestUtil.getResourceURI(ingredient.getId()))
.body(ingredient);
return re;
}
Ingredient is an entity class with some fields:
public class Ingredient implements Serializable
{
#Id
private Integer id;
private Meal meal;
private Grocery grocery;
private Float amount;
...
}
RequestUtil takes care of creating the URI where the newly created resource is to be found:
public class RequestUtil
{
public static URI getResourceURI(int id) throws URISyntaxException
{
final String url = RequestUtil.getCurrentRequest().getRequestURL().toString();
final String req = RequestUtil.omitLast(url);
return new URI(req + "get/" + id);
}
public static HttpServletRequest getCurrentRequest()
{
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return ((ServletRequestAttributes) requestAttributes).getRequest();
}
public static String omitLast(final String url) {
return url.substring(0, url.lastIndexOf("/") + 1);
}
}
The http status code and resource URI end up correctly in the response headers, but the body contains two JSONs:
{
"id": 407,
"meal": {
"id": 99,
"name": "New Meal",
"active": true
},
"grocery": null,
"amount": null,
"bought": false
} {
"timestamp": "2018-08-29T19:25:31.466+0000",
"status": 201,
"error": "Created",
"message": "No message available",
"path": "/ingredient/add"
}
Our javascript code does not expect this extra data and fails with
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data at line 1 column 114 of the JSON data
Using a debugger, we can see that by the time the code reaches the return statement in add(), the ResponseEntity does not contain this extra data. Can someone explain where it comes from, and how we stop it from polluting the response?
Thanks for any help!

How to Receive Webhook from Stripe in Java

I am trying to receive a webhook via a post request from Stripe Payments. The java method to process it looks like this:
#ResponseBody
#RequestMapping( consumes="application/json",
produces="application/json",
method=RequestMethod.POST,
value="stripeWebhookEndpoint")
public String stripeWebhookEndpoint(Event event){
logger.info("\n\n" + event.toString());
logger.info("\n\n" + event.getId());
return null;
}
But the Stripe Event always comes back with all null values:
<com.stripe.model.Event#315899720 id=null> JSON: {
"id": null,
"type": null,
"user_id": null,
"livemode": null,
"created": null,
"data": null,
"pending_webhooks": null
}
If the method receives a String instead,and using #RequestBody:
#ResponseBody
#RequestMapping( consumes="application/json",
produces="application/json",
method=RequestMethod.POST,
value="stripeWebhookEndpoint")
public String stripeWebhookEndpoint(#RequestBody String json){
logger.info(json);
return null;
}
Here, it prints the json without null values. Here's part of the request being printed:
{
"created": 1326853478,
"livemode": false,
"id": "evt_00000000000000",
"type": "charge.succeeded",
"object": "event",
"request": null,
"data": {
"object": {
"id": "ch_00000000000000",
"object": "charge",
"created": 1389985862,
"livemode": false,
"paid": true,
"amount": 2995,
"currency": "usd",
...
}
But using #RequestBody with a Stripe Event parameter gives a 400: bad syntax.
So why can't I take in the correct type, a Stripe Event, as the parameter?
Here's what I did:
The Java method still takes in the Event as a json String. Then I used Stripe's custom gson adapter and got the Event with:
Event event = Event.gson.fromJson(stripeJsonEvent, Event.class);
Where stripeJsonEvent is the string of json taken in by the webhook endpoint.
public String stripeWebhookEndpoint(#RequestBody String json, HttpServletRequest request) {
String header = request.getHeader("Stripe-Signature");
String endpointSecret = "your stripe webhook secret";
try {
event = Webhook.constructEvent(json, header, endpointSecret);
System.err.println(event);
} catch (SignatureVerificationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//
enter code here
return "";
}
I have been looking for the same answer, so after looking at their own code, here is how they actually do it:
String rawJson = IOUtils.toString(request.getInputStream());
Event event = APIResource.GSON.fromJson(rawJson, Event.class);
APIResource comes from their library (I am using 1.6.5)
In order to abstract all of the deserialization logic out of the controller I did the following:
Created a custom deserializer
public class StripeEventDeserializer extends JsonDeserializer<Event> {
private ObjectMapper mapper;
public StripeEventDeserializer(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public Event deserialize(JsonParser jp, DeserializationContext context) throws IOException {
ObjectNode root = mapper.readTree(jp);
Event event = ApiResource.GSON.fromJson(root.toString(), Event.class);
return event;
}
}
I then needed to add that deserializer to my ObjectMapper config:
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Event.class, new StripeEventDeserializer(mapper));
mapper.registerModule(simpleModule);
I could then use #RequestBody correctly on the Spring rest controller:
#PostMapping("/webhook")
public void webhook(#RequestBody Event stripeEvent)

Categories

Resources