MappingJackson2HttpMessageConverter Mistake on Converting Complex Object - java

i have Class Response that return to client for every request from client my Response is:
public class Response<T> extends Request{
private ResponseType responseType;
private String message;
private ArrayList<T> result;
private int activationCode;
.
.
.
}
in my server side i have method that return Response that contains results with arraylist of InstagramUser
my method:
public Response getUserByUserName(#RequestBody List<Request> requests){
.
.
.
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
try {
String searchResponse = restTemplate.getForObject(uri, String.class);
JSONObject rawObject = new JSONObject(searchResponse);
JSONArray searchResults = rawObject.getJSONArray("data");
ArrayList<InstagramUser> users = new ArrayList<InstagramUser>();
for (int i = 0; i < searchResults.length(); i++) {
JSONObject result = searchResults.getJSONObject(i);
users.add(new InstagramUser(result, AccessToken.getTokenString()));
}
response = new Response(requests.get(0).getUserId(), ResponseType.success, "users find successfully on: " + new Date().toString());
response.setResult(users);
.
.
.
return response;
}
and my InstagramUser:
public class InstagramUser extends InstagramModel {
protected long id;
protected String userName;
protected String fullName;
protected String profilePictureURI;
protected String bio;
protected String website;
protected int mediaCount = -1;
protected int followerCount = -1;
protected int followingCount = -1;
...
}
but in client side when i get Response from server my results is ArrayList of LinkedHashMap insted of ArrayList of InstagramUser:
restTemplate.postForObject(URL + conditions, params,Response.class);
and this is my json response from server for calling this method:
{
"id": 6151638910251304448,
"userId": 2,
"instagramId": null,
"searchId": null,
"mediaId": null,
"phoneNumber": null,
"name": null,
"date": 1466665008687,
"responseType": "success",
"message": "users find successfully on: Thu Jun 23 11:26:48 IRDT 2016",
"result": [
{
"id": 110000004535,
"userName": "______etabdar",
"fullName": "________dar",
"profilePictureURI": "https://igcdn-photos-h-a.akamaihd.net/hphotos-ak-xap1/t51.2885-19/s150x150/13183XXXXXXXX0231_1584363729_a.jpg",
"bio": "🎓 XXXXX 90",
"website": "",
"mediaCount": -1,
"followerCount": -1,
"followingCount": -1
}
],
"activationCode": 0
}
how can i fix this?

you'll have to use parameterized type reference. Works only with rest template exchange methods.
List<YourObjectType>> res = template.exchange(
rootUrl,
HttpMethod.POST,
null,
new ParameterizedTypeReference<List<YourObjectType>>() {});
Adjust parameters based on your inputs.

It looks like
restTemplate.postForObject(URL + conditions, params,Response.class)
doesn't know the specific type this part of your servers response:
"result": [
{
"id": 110000004535,
"userName": "______etabdar",
"fullName": "________dar",
"profilePictureURI": "https://igcdn-photos-h-a.akamaihd.net/hphotos-ak-xap1/t51.2885-19/s150x150/13183XXXXXXXX0231_1584363729_a.jpg",
"bio": "🎓 XXXXX 90",
"website": "",
"mediaCount": -1,
"followerCount": -1,
"followingCount": -1
}
],
should be mapped to when assigning
private ArrayList<T> result;
of your Response class, therefore switching to a default type (probably LinkedHashMap).
Maybe the answer to the following SO-question is of any help:
Using Spring RestTemplate in generic method with generic parameter

Related

Resttemplate 422 Unprocessable Entity Expected a list of items but got type \"dict\"

I have a post service with collection on Postman. I can successfully get a response with Postman. This service is a bit complicated with its body and headers. I want to call this service from SpringBoot using Resttemplate. But I am getting the error mentioned in the title. I'm probably making a mistake somewhere in the request list or sending the wrong data type, but I have no idea and I've been trying for a long time. I hope you can help me.
First of all,
I will share the raw body that I post request with Postman:
{
"currency": "EUR",
"customer_country": "NL",
"customer_language": "en",
"partner_metadata": {
"sales_channel": "inPath",
"device":"app"
},
"request": [
{
"policy_type": "comprehensive_travel_insurance",
"policy_type_version": "7",
"is_return": false,
"policy_start_date": "2022-06-07T10:22:30.925568+01:00",
"policy_end_date": "2022-06-13T20:55:00+02:00",
"departure_country": "LT",
"destination_country": "EE",
"total_tickets_price": 78.99,
"number_of_adults": 1,
"number_of_children": 0,
"number_of_infants": 0,
"trip_start_date": "2022-06-13T19:45:00+02:00",
"trip_end_date": "2022-06-13T20:55:00+02:00",
"flights": [
{
"legs": [
{
"departure_datetime": "2022-06-13T19:45:00+02:00",
"arrival_datetime": "2022-06-13T20:55:00+02:00",
"flight_number": "BT905",
"marketing_airline_iata_code": "BT",
"operating_airline_iata_code": "BT",
"departure_airport": "VNO",
"arrival_airport": "TLL",
"departure_country": "LT",
"arrival_country": "EE"
}
],
"departure_datetime": "2022-06-13T19:45:00+02:00",
"arrival_datetime": "2022-06-13T20:55:00+02:00",
"departure_country": "LT",
"arrival_country": "EE",
"departure_city": "VNO",
"arrival_city": "TLL"
}
]
}
]
}
Headers on Postman:
Example endpoint where I call the service:
#ResponseBody
#GetMapping("/abc")
public ResponseEntity<String> postQuotes(){
final HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("X-Api-Key", "****");
headers.add("Date", "Thu, 02 Jun 2022 22:16:39 GMT");
String signatureKeyID = "Signature keyId=\"****\",";
String algoritm = "algorithm=\"hmac-sha1\",";
String signature = "signature=\"***"";
String authHeader = signatureKeyID.concat(algoritm).concat(signature);
headers.add("Authorization", authHeader);
HttpEntity<QuoteRequest> entity = new HttpEntity<>(fillQuoteRequest(), headers);
ResponseEntity<String> postForEntity = rest.postForEntity(quoteURL, entity, String.class);
return postForEntity;
}
Methods where I set Entity objects:
private Legs fillLegs(){
Legs legs = new Legs();
legs.setDepartureDatetime("2022-06-13T19:45:00+02:00");
legs.setArrivalDatetime("2022-06-13T20:55:00+02:00");
legs.setFlightNumber("BT905");
legs.setOperatingAirlineIataCode("BT905");
legs.setMarketingAirlineIataCode("BT905");
legs.setDepartureAirport("VNO");
legs.setArrivalAirport("TLL");
legs.setArrivalCountry("LT");
legs.setDepartureCountry("EE");
return legs;
}
private Flights fillFlights(){
Flights flights = new Flights();
List<Legs> legsList = new ArrayList<>();
legsList.add(fillLegs());
flights.setLegs(legsList);
flights.setDepartureDateTime("2022-06-13T19:45:00+02:00");
flights.setArrivalDateTime("2022-06-13T19:45:00+02:00");
flights.setDepartureCountry("LT");
flights.setArrivalCountry("EE");
flights.setDepartureCity("VNO");
flights.setArrivalCity("TLL");
return flights;
}
private QRequest fillQrequest(){
QRequest qRequest = new QRequest();
List<Flights> flightsList = new ArrayList<>();
flightsList.add(fillFlights());
qRequest.setFlights(flightsList);
qRequest.setPolicyType("comprehensive_travel_insurance");
qRequest.setPolicyTypeVersion("7");
qRequest.setIsReturn(false);
qRequest.setPolicyStartDate("2022-06-07T10:22:30.925568+01:00");
qRequest.setPolicyEndDate("2022-06-13T20:55:00+02:00");
qRequest.setDepartureCountry("LT");
qRequest.setDestinationCountry("EE");
qRequest.setTotalTicketsPrice(78.99f);
qRequest.setNumberOfAdults(1);
qRequest.setNumberOfChildren(0);
qRequest.setNumberOfInfants(0);
qRequest.setTripStartDate("2022-06-13T19:45:00+02:00");
qRequest.setTripEndDate("2022-06-13T20:55:00+02:00");
return qRequest;
}
private PartnerMetadata fillPartnerMeta(){
PartnerMetadata pm = new PartnerMetadata();
pm.setDevice("app");
pm.setSalesChannel("inPath");
return pm;
}
private QuoteRequest fillQuoteRequest(){
QuoteRequest qr = new QuoteRequest();
qr.setRequest(fillQrequest());
qr.setPartnerMetadata(fillPartnerMeta());
qr.setCurrency("EUR");
qr.setCustomerCountry("NL");
qr.setCustomerLanguage("en");
return qr;
}
As a result of all, the error returned when I call the service:
org.springframework.web.client.HttpClientErrorException$UnprocessableEntity: 422 Unprocessable Entity: "{"type":"validation_error","message":"An API error occurred.","errors":{"request":["Expected a list of items but got type \"dict\"."]}}"
Can you help me find the source of the error?
As far as I can see flights and legs are supposed to be sent as list, but you are sending them as objects.

Spring Boot (2.3.6.RELEASE) Deserialization Fails when using RestTemplate and Unwrap Root

I'm trying to consume an API using RestTemplate but it will simply not deserialize the json response into my pojo
Here is the json payload I'm trying to deserialize:
"Response": {
"Count": 77,
"Data": [
{
"AllowDelete": "1",
"ContactCount": 1482,
"CreatedDate": "Dec 01, 2020",
"ID": "17991951",
"IsImporting": "0",
"IsMasterUnsubscribe": "0",
"ListAudited": "1",
"ListDescription": "City of Markham Staff - December 2020 (LATEST)",
"ListImportV3": "1",
"ListType": "0",
"ModifiedDate": "Dec 03, 2020",
"Name": "City of Markham Staff - December 2020 (LATEST)",
"NameShort": "City of Markham Staff - December 2020 (LATEST)",
"PermissionPassList": "0",
"Segments": [],
"Status": ""
},{
"AllowDelete": "0",
"ContactCount": 884,
"CreatedDate": "Nov 04, 2011",
"ID": "582203",
"IsImporting": "0",
"IsMasterUnsubscribe": "1",
"ListAudited": "1",
"ListDescription": "Master Unsubscribe List",
"ListImportV3": "0",
"ListType": "0",
"ModifiedDate": "Dec 04, 2020",
"Name": "Master Unsubscribe List",
"NameShort": "Master Unsubscribe List",
"PermissionPassList": "0",
"Segments": [],
"Status": ""
}
],
"Status": "1"
}
}
Here is my main pojo:
package com.markham.enews.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName(value = "Response")
public class Contact {
//Total number
private int count;
//1 if successful, -1 if error
private String status;
// Further details of the Contact List
private List<ContactFullRecord> data;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<ContactFullRecord> getData() {
return data;
}
public void setData(List<ContactFullRecord> data) {
this.data = data;
}
#Override
public String toString() {
return "Contact [count=" + count + ", status=" + status + ", data=" + data + "]";
}
}
As per this stack overflow link Spring Boot Jackson with Root name
I added the following to my application.properties:
spring.jackson.mapper.accept-case-insensitive-properties=true
spring.jackson.deserialization.unwrap-root-value=true
My rest controller get method is as follows:
#GetMapping(value = "/ContactTest")
private Contact getContactTest() {
String uri = "https://clientapi.benchmarkemail.com/Contact/";
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);
Contact contact = response.getBody();
return contact;
}
But the resulting object has all empty/null values:
"count": 0,
"status": null,
"data": null
I think the unwrap root and/or case insensitive properties are not being picked up..
If I write the following unit test and use objectMapper directly, it works:
#Test
public void wrapRootValue() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
String str = "{ \"Response\": {\"Count\": 77,\"Data\": [{\"AllowDelete\": \"0\",\"ContactCount\": 884,\"CreatedDate\": \"Nov 04, 2011\",\"ID\": \"582203\",\"IsImporting\": \"0\",\"IsMasterUnsubscribe\": \"1\",\"ListAudited\": \"1\",\"ListDescription\": \"Master Unsubscribe List\",\"ListImportV3\": \"0\",\"ListType\": \"0\",\"ModifiedDate\": \"Dec 03, 2020\",\"Name\": \"Master Unsubscribe List\",\"NameShort\": \"Master Unsubscribe List\",\"PermissionPassList\": \"0\",\"Segments\": [],\"Status\": \"\"}],\"Status\": \"1\"}}";
Contact root = mapper.readValue(str, Contact.class);
System.out.println(root);
}
Output:
Contact [count=77, status=1, data=[ContactFullRecord [id=582203, name=Master Unsubscribe List, nameShort=Master Unsubscribe List, status=, contactCount=884.0, createdDate=Nov 04, 2011, modifiedDate=Dec 03, 2020, permissionPassList=0, listAudited=1, listDescription=Master Unsubscribe List, isImporting=0, isMasterUnsubscribe=1, allowDelete=0, listImportV3=0]]]
Any help would be greatly appreciated!
Use spring boot pre configured RestTemplateBuilder ( has all the jackson message converter configuration applied ) and use build to request new RestTemplate instance.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
}
Autowire the instance into controller class.
#Autowired
private RestTemplate restTemplate;
#GetMapping(value = "/ContactTest")
private Contact getContactTest() {
String uri = "https://clientapi.benchmarkemail.com/Contact/";
HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);
Contact contact = response.getBody();
return contact;
}
You can also look at https://www.baeldung.com/spring-rest-template-builder for other set ups.
The problem is that you are configuring the Jackson deserialization behavior at the Spring Boot level, you are not configuring the deserialization behavior for your RestTemplate.
One possible approach you can follow is the one suggested by #s7vr in his/her answer, and reuse the Spring Boot provided configuration.
If you only want to customize the Jackson configuration for your RestTemplate you can do it with something like:
final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
// Base converters
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter(false));
messageConverters.add(new SourceHttpMessageConverter<>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
// Custom Jackson Converter
final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
final ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
messageConverters.add(mappingJackson2HttpMessageConverter);
final RestTemplate restTemplate = new RestTemplate(messageConverters);
// Use it as you consider appropriate
String uri = "https://clientapi.benchmarkemail.com/Contact/";
HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);
Contact contact = response.getBody();
//...
Of course, you can reuse this configuration if needed by configuring a FactoryBean for RestTemplate and inject later in your controller, for instance.

Parsing HttpResponse into customized object

I'm using some weather api that returns actual forecast for given city.
I would like to parse HtmlResponse into my object which looks like this:
public class Weather {
String countryCode;
String city;
double temperature;
double sensedTemperature;
int humidity;
int windSpeed; // meter/sec
int windDirection;
int pressure;
int weatherDescription;
}
Method that does whole operations:
#GetMapping("/weather")
public void getCurrentWeather(#RequestParam String city, #RequestParam(required = false) String lang,
#RequestParam(required = false) String units)
throws UnirestException, UnsupportedEncodingException {
//Params
String query = getQueryAccordingToGivenParameters(city, lang, units);
HttpResponse<JsonNode> response = Unirest.get(HOST + "?" + query)
.header("x-rapidapi-host", X_RAPID_HOST)
.header("x-rapidapi-key", X_RAPIDAPI_KEY)
.asJson();
System.out.println(response.getBody());
}
Right now it prints response in command line, but I would like to convert it to Weather object.
JSON looks like this:
{
"visibility": 10000,
"timezone": 0,
"main": {
"temp": 7.21,
"temp_min": 5.56,
"humidity": 81,
"pressure": 1029,
"feels_like": 4.87,
"temp_max": 9
},
"clouds": {
"all": 75
},
"sys": {
"country": "GB",
"sunrise": 1577433953,
"sunset": 1577462200,
"id": 1414,
"type": 1
},
"dt": 1577444681,
"coord": {
"lon": -0.13,
"lat": 51.51
},
"weather": [
{
"icon": "04d",
"description": "broken clouds",
"main": "Clouds",
"id": 803
}
],
"name": "London",
"cod": 200,
"id": 2643743,
"base": "stations",
"wind": {
"speed": 1.5
}
}
I need
"name" - london,
"wind" - speed - 1.5
"weather" - description - "broken clouds"
"main" - temp - 7.21
"main" - humidity - 81
and some others but these are just examples.
I tried to use JSONObject and get wind speed like this:
JSONObject object = new JSONObject(response);
JSONObject windObject = new JSONObject(object);
String wind = windObject.getString("wind");
System.out.println(wind);
but I got org.json.JSONException: JSONObject["wind"] not found.
Could you tell me how to fetch the speed of the wind and how to fetch description weather? The rest I should do by my own.
//within Andreas answer code looks like this:
package weatherapp;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.json.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
#RestController
public class WeatherController {
public static final String X_RAPID_HOST = "community-open-weather-map.p.rapidapi.com";
public static final String X_RAPIDAPI_KEY = "...";
public static final String CHARSET = "UTF-8";
public static final String HOST = "https://community-open-weather-map.p.rapidapi.com/weather";
//{city}/{lang}/{units]
#GetMapping("/weather")
public void getCurrentWeather(#RequestParam String city, #RequestParam(required = false) String lang,
#RequestParam(required = false) String units)
throws UnirestException, UnsupportedEncodingException {
//Params
String query = getQueryAccordingToGivenParameters(city, lang, units);
HttpResponse<JsonNode> response = Unirest.get(HOST + "?" + query)
.header("x-rapidapi-host", X_RAPID_HOST)
.header("x-rapidapi-key", X_RAPIDAPI_KEY)
.asJson();
JSONObject root = new JSONObject(response);
JSONObject wind = root.getJSONObject("wind");
double windSpeed = wind.getDouble("speed");
System.out.println(windSpeed);
}
...
}
but it throws:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.json.JSONException: JSONObject["wind"] not found.] with root cause
org.json.JSONException: JSONObject["wind"] not found.
at org.json.JSONObject.get(JSONObject.java:471) ~[json-20160212.jar:na]
at org.json.JSONObject.getJSONObject(JSONObject.java:636) ~[json-20160212.jar:na]
at weatherapp.WeatherController.getCurrentWeather(WeatherController.java:42) ~[classes/:na]
...
After debugging root looks like this:
UPDATED
It really helps when you read the documentation of the API you're using. Providing relevant links to javadoc below!!!
Since response is an HttpResponse<JsonNode> object, you need to call getBody() to get the body of the response, call getObject() on the already parsed JsonNode object to get the root JSONObject object, call getJSONObject to get the nested wind object, and finally call getDouble to get the speed value.
JsonNode rootNode = response.getBody();
JSONObject rootObj = rootNode.getObject();
JSONObject windObj = rootObj.getJSONObject("wind");
double windSpeed = windObj.getDouble("speed");
You can fetch wind speed with:
JSONObject json = new JSONObject(response);
JSONObject wind = json.getJSONObject("wind");
double speed = wind.getDouble("speed");
And the weather description as follows:
JSONObject json = new JSONObject(response);
JSONObject weather = json.getJSONArray("weather").getJSONObject(0);
String description = weather.getString("description");
your json output shows that "wind" key is an object and you are trying to get wind as string.
Use this
JSONObject object = new JSONObject(response);
JSONObject windObject = object.getJSONObject("wind");
String speed = windObject.getString("speed");
System.out.println(speed);
I needed to do this:
HttpResponse<JsonNode> response = Unirest.get(HOST + "?" + query)
.header("x-rapidapi-host", X_RAPID_HOST)
.header("x-rapidapi-key", X_RAPIDAPI_KEY)
.asJson();
JSONObject z = response.getBody().getObject();
JSONObject zz = z.getJSONObject("wind");
double wind = zz.getDouble("speed");

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