I need to receive a request from a webhook from a third party API.
Post content is an urlenconded in following format:
event=invoice.created&data%5Bid%5D=value1&data%5Bstatus%5D=pending&data%5Baccount_id%5D=value2
The problem is serialize this params data[id] with these square brackets. I'm getting an error in spring boot:
Invalid property 'data[account_id]' of bean class [br.com.bettha.domain.dto.IuguWebhookDto]: Property referenced in indexed property path 'data[account_id]' is neither an array nor a List nor a Map; returned value was [IuguDataDto(id=null, account_id=null, status=null, subscription_id=null)]
My controller:
#PostMapping(value = "/subscription-invoice", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
#ApiOperation(
value="Create a subscription invoice from Iugu's webhook",
response= Invoice.class,
notes="This Operation creates a subscription invoice from Iugu's webhook")
#PreAuthorize("#oauth2.hasScope('read')")
public ResponseEntity<Invoice> createSubscriptionInvoice(IuguWebhookDto iuguWebhookDto) {
try {
Invoice invoice = paymentService.createSubscriptionInvoiceFromIugusWebhook(iuguWebhookDto);
return new ResponseEntity<>(invoice, HttpStatus.CREATED);
} catch (EntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage(), e);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
}
}
IuguWebhookDto.java:
#Getter
#Setter
#NoArgsConstructor
#ToString
public class IuguWebhookDto implements Serializable {
private static final long serialVersionUID = -5557936429069206933L;
private String event;
private IuguDataDto data;
IuguDataDto.java:
#Getter
#Setter
#NoArgsConstructor
#ToString
public class IuguDataDto implements Serializable {
private static final long serialVersionUID = -5557936429069206933L;
private String id;
private String account_id;
private String status;
private String subscription_id;
How can I receive these request params as an object in Spring Boot?
I have the same problem using Iugu Webhooks API. To solve, i just stringify the raw data sent by Iugu, remove the unwanted characters, and then parse again the object to get the variables that i want.
var dataReceived = JSON.stringify(req.body).replace('data[id]','id').replace('data[status]','status').replace('data[account_id]','account_id');
var finalData = JSON.parse(dataReceived);
return res.status(200).send(finalData.id);
Related
I am using SpringBoot Java 11 and have a REST endpoint:
REST Service
#Operation(summary = "Endpoint called by the UI to get the journal entries (using the posted form)")
#PostMapping(path = "/journal-entries/{memberId}/{firstRow}/{noRows}/{overrideFilterForMerchants}")
public ResponseEntity<PowWowControllerDTO> getEntries(#RequestBody PowWowDashboardDTO powWowDashboardDTO, #Parameter(description="memberId") #NotBlank #PathVariable final Long memberId, #Parameter(description="first row") #NotBlank #PathVariable final int firstRow, #Parameter(description="no rows") #NotBlank #PathVariable final int noRows, #Parameter(description="has override role") #NotBlank #PathVariable final boolean overrideFilterForMerchants) {
PowWowControllerDTO dto = new PowWowControllerDTO();
try {
dto = powWowJournalService.getJournalEntries(powWowDashboardDTO, memberId, firstRow, noRows, overrideFilterForMerchants);
dto.setStatus("Success");
} catch (Exception e) {
dto.setStatus("Error: "+e.getMessage());
logger.error("Exception in REST endpoint: getEntries", e);
}
return new ResponseEntity(dto, HttpStatus.OK);
}
When this endpoint is called, in debug, I can see that the PowWowControllerDTO is populated as expected.
I have an old Struts Java 7 application that needs to consume the above endpoint:
REST Client
PowWowControllerDTO dto = new PowWowControllerDTO();
String jsonString = response.getText();
if (jsonString != null) {
logger.info(jsonString);
Gson gson = new Gson();
dto = gson.fromJson(jsonString, PowWowControllerDTO.class);
This gets an exception trying to convert the response to a DTO.
Error
Error to get data from response:
com.google.gson.stream.MalformedJsonException: Expected EOF at line 1
column 48448
I can see from the jsonString that there is a response which looks like a number of empty objects, that does contain:
"message":"Could not write JSON: Infinite recursion
(StackOverflowError); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Infinite
recursion (StackOverflowError)
PowWowControllerDTO
public class PowWowControllerDTO {
private List<PowWowJournalEntities> journals = new ArrayList<>();
private int firstRow;
private int lastRow;
private Long noRows;
private String status;
PowWowJournalEntities
public class PowWowJournalEntities {
private PowWowJournalHeaderEntity powWowJournalHeaderEntity;
private List<PowWowTransactionEntity> powWowTransactionEntities = new ArrayList<>();
private List<PowWowAuditTrailEntity> powWowAuditTrailEntities = new ArrayList<>();
private PowWowJournalEntryObjectEntity powWowJournalEntryObjectEntity;
PowWowJournalHeaderEntity
#Entity
#Table(name = "powwowjournalheader")
public class PowWowJournalHeaderEntity {
My springboot model is this:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Document(collection = "products")
public class Product {
#Id
private String id;
#NotEmpty(message = "name is mandatory")
private String name;
#NotEmpty(message = "price is mandatory")
private int price;
private MultipartFile file;
}
#AllArgsConstructor
#Data
#Builder
public class AddProductCommand implements Serializable {
#TargetAggregateIdentifier
private String id;
#NotNull(message = "no product details were supplied")
#Valid
private Product product;
public AddProductCommand(){
}
}
Since I have to send a MultipartFile from the reactjs, I must use FormData. I have tried the following:
async handleSubmit(event) {
event.preventDefault();
try {
let product = new FormData();
product.append("name", this.state.name);
product.append("price", this.state.price);
product.append("file", this.state.file);
let addProductCommand = new FormData();
addProductCommand.append("product", product);
const response = await axios.post(SELL_URL, addProductCommand , {
headers: {
'Content-Type': 'multipart/form-data'
}
});
this.clearState();
event.target.reset();
this.props.history.push("/buy");
} catch (err) {
console.log(JSON.stringify(err));
}
}
However, I got following error in the springboot:
DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'addProductCommand' on field 'product': rejected value [[object FormData]]; codes [typeMismatch.addProductCommand.product,typeMismatch.product,typeMismatch.com.cognizant.user.core.models.Product,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addProductCommand.product,product]; arguments []; default message [product]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.cognizant.user.core.models.Product' for property 'product'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.cognizant.user.core.models.Product' for property 'product': no matching editors or conversion strategy found]]
What have I missed and how should I fix this error?
Add controller code. I am trying to implement it as CQRS design. Here is the controller. I nedd to write more description not much code. Otherwise stackoverflow doesn't allow me do add more code :(.
#RestController
#RequestMapping(path = "/api/v1/addProduct")
public class AddProductController {
private final CommandGateway commandGateway;
#Autowired
public AddProductController(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
#PostMapping
public ResponseEntity<AddProductResponse> registerUser(#Valid #ModelAttribute AddProductCommand command) {
var id = UUID.randomUUID().toString();
command.setId(id);
try {
commandGateway.sendAndWait(command);
return new ResponseEntity<>(new AddProductResponse(id, "Product added successfully!"), HttpStatus.CREATED);
} catch (Exception e) {
var safeErrorMessage = "Error while processing add product request for id - " + id;
System.out.println(e);
return new ResponseEntity<>(new AddProductResponse(id, safeErrorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
for the above scenario, you are appending form data to the product key addProductCommand.append("product", product);
so in your controller, you should get the product entity. add this to your controller; #ModelAttribute("product")
public ResponseEntity<AddProductResponse> registerUser(#Valid #ModelAttribute("product") AddProductCommand command)
I am using spring boot's Rest Controller for creating rest end points. Along with swagger 2 for api documentation.
#RestController
#RequestMapping("/api")
public class BatchController extends ControllerConfig {
#PostMapping("/batch")
public GeneralResponse<Boolean> createBatch(#RequestBody Batch batch) throws Exception{
try{
batchService.createBatch(batch);
return new GeneralResponse<>(true,"batch created successfully", true, System.currentTimeMillis(), HttpStatus.OK);
} catch (Exception e){
return new GeneralResponse<>(false,e.getMessage(), false, System.currentTimeMillis(), HttpStatus.BAD_REQUEST);
}
}
#PutMapping("/batch")
public GeneralResponse<Boolean> updateBatch(#RequestBody Batch batch) {
try {
batchService.updateBatch(batch);
return new GeneralResponse<>(true, "batch updated successfully", true, System.currentTimeMillis(), HttpStatus.OK);
} catch (Exception e) {
return new GeneralResponse<>(false, e.getMessage(), false, System.currentTimeMillis(), HttpStatus.BAD_REQUEST);
}
}
}
And Batch Model :
#Entity
#Table
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Batch {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long qualityId;
private Date date;
private String remark;
}
I am using JPA repository.
Now, For both the rest end points Swagger will show the request model as :
{
id: 0,
qualityId: 0,
date: "2020-10-04T21:18:00.656Z",
remark: "string"
}
but I want to hide "id" field for create batch request as that is autogenerated, but its required for update as that is based on id.
how can that be done?
Entities are not supposed to be exposed in the API layer,
You should create a dedicated DTO classes instead.
For example-
#Data
public class PutBatchDTO {
private Long id;
private Long qualityId;
private Date date;
private String remark;
}
#Data
public class PostBatchDTO {
private Long qualityId;
private Date date;
private String remark;
}
I am trying to parse a jcelulas object that has a few many to one relationships. I can't seem to parse them correctly. Any help is appreciated.
Model:
#Entity
#Table(name = "jcelulas", catalog = "7jogos")
public class Jcelulas implements java.io.Serializable {
private Integer id;
private Jconcorrentes jconcorrentes;
private Jgrelhas jgrelhas;
private Jcodigos jcodigos;
private Jpremios jpremios;
private boolean checked;
private Date dataChecked;
// getters and setters
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "ConcorrentesId")
public Jconcorrentes getJconcorrentes() {
return this.jconcorrentes;
}
public void setJconcorrentes(Jconcorrentes jconcorrentes) {
this.jconcorrentes = jconcorrentes;
}
}
Controller:
#RequestMapping(value = "/jtabuleiros/play/commit",
method = RequestMethod.POST,
headers = {"Content-type=application/json"})
#ResponseBody
public JsonResponse playcelula(#ModelAttribute DataJson celula,#RequestBody String json) {
System.out.println(celula.toString());
System.out.println(json);
ObjectMapper mapper = new ObjectMapper();
try {
// read from file, convert it to user class
Jcelulas user = mapper.readValue(json, Jcelulas.class);
// display to console
System.out.println(user);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new JsonResponse("OK","");
}
Request:
{
"id":1,
"jconcorrentes":1,
"jgrelhas":1,
"jcodigos":1
}
How should I parse jconcorrentes? I tried as an int and got the following error:
org.codehaus.jackson.map.JsonMappingException: Can not instantiate value of type [simple type, class com.setelog.spring.model.Jconcorrentes] from JSON integral number; no single-int-arg constructor/factory method (through reference chain: com.setelog.spring.model.Jcelulas["jconcorrentes"])
Jconcorrentes:
#Entity
#Table(name = "jconcorrentes", catalog = "7jogos")
public class Jconcorrentes implements java.io.Serializable {
private Integer id;
....
private Date dataRegisto;
private Set<Jcelulas> jcelulases = new HashSet<Jcelulas>(0);
}
PS: These models were generated with hibernate from the mysql database
The problem is that jconcorrentes is being serialized as a number, not JSON Object. So Jackson does not know how to construct a Jconcorrentes out of value 1.
We have a big table with a lot of columns. After we moved to MySQL Cluster, the table cannot be created because of:
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 14000. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs
As an example:
#Entity #Table (name = "appconfigs", schema = "myproject")
public class AppConfig implements Serializable
{
#Id #Column (name = "id", nullable = false)
#GeneratedValue (strategy = GenerationType.IDENTITY)
private int id;
#OneToOne #JoinColumn (name = "app_id")
private App app;
#Column(name = "param_a")
private ParamA parama;
#Column(name = "param_b")
private ParamB paramb;
}
It's a table for storing configuration parameters. I was thinking that we can combine some columns into one and store it as JSON object and convert it to some Java object.
For example:
#Entity #Table (name = "appconfigs", schema = "myproject")
public class AppConfig implements Serializable
{
#Id #Column (name = "id", nullable = false)
#GeneratedValue (strategy = GenerationType.IDENTITY)
private int id;
#OneToOne #JoinColumn (name = "app_id")
private App app;
#Column(name = "params")
//How to specify that this should be mapped to JSON object?
private Params params;
}
Where we have defined:
public class Params implements Serializable
{
private ParamA parama;
private ParamB paramb;
}
By using this we can combine all columns into one and create our table. Or we can split the whole table into several tables. Personally I prefer the first solution.
Anyway my question is how to map the Params column which is text and contains JSON string of a Java object?
You can use a JPA converter to map your Entity to the database.
Just add an annotation similar to this one to your params field:
#Convert(converter = JpaConverterJson.class)
and then create the class in a similar way (this converts a generic Object, you may want to specialize it):
#Converter(autoApply = true)
public class JpaConverterJson implements AttributeConverter<Object, String> {
private final static ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convertToDatabaseColumn(Object meta) {
try {
return objectMapper.writeValueAsString(meta);
} catch (JsonProcessingException ex) {
return null;
// or throw an error
}
}
#Override
public Object convertToEntityAttribute(String dbData) {
try {
return objectMapper.readValue(dbData, Object.class);
} catch (IOException ex) {
// logger.error("Unexpected IOEx decoding json from database: " + dbData);
return null;
}
}
}
That's it: you can use this class to serialize any object to json in the table.
The JPA AttributeConverter is way too limited to map JSON object types, especially if you want to save them as JSON binary.
You don’t have to create a custom Hibernate Type to get JSON support, All you need to do is use the Hibernate Types OSS project.
For instance, if you're using Hibernate 5.2 or newer versions, then you need to add the following dependency in your Maven pom.xml configuration file:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Now, you need to declare the new type either at the entity attribute level or, even better, at the class level in a base class using #MappedSuperclass:
#TypeDef(name = "json", typeClass = JsonType.class)
And the entity mapping will look like this:
#Type(type = "json")
#Column(columnDefinition = "json")
private Location location;
If you're using Hibernate 5.2 or later, then the JSON type is registered automatically by MySQL57Dialect.
Otherwise, you need to register it yourself:
public class MySQLJsonDialect extends MySQL55Dialect {
public MySQLJsonDialect() {
super();
this.registerColumnType(Types.JAVA_OBJECT, "json");
}
}
And, set the hibernate.dialect Hibernate property to use the fully-qualified class name of the MySQLJsonDialect class you have just created.
If you need to map json type property to json format when responding to the client (e.g. rest API response), add #JsonRawValue as the following:
#Column(name = "params", columnDefinition = "json")
#JsonRawValue
private String params;
This might not do the DTO mapping for server-side use, but the client will get the property properly formatted as json.
It is simple
#Column(name = "json_input", columnDefinition = "json")
private String field;
and in mysql database your column 'json_input' json type
There is a workaround for those don't want write too much code.
Frontend -> Encode your JSON Object to string base64 in POST method, decode it to json in GET method
In POST Method
data.components = btoa(JSON.stringify(data.components));
In GET
data.components = JSON.parse(atob(data.components))
Backend -> In your JPA code, change the column to String or BLOB, no need Convert.
#Column(name = "components", columnDefinition = "json")
private String components;
In this newer version of spring boot and MySQL below code is enough
#Column( columnDefinition = "json" )
private String string;
I was facing quotes issue so I commented below line in my project
#spring.jpa.properties.hibernate.globally_quoted_identifiers=true
I had a similar problem, and solved it by using #Externalizer annotation and Jackson to serialize/deserialize data (#Externalizer is OpenJPA-specific annotation, so you have to check with your JPA implementation similar possibility).
#Persistent
#Column(name = "params")
#Externalizer("toJSON")
private Params params;
Params class implementation:
public class Params {
private static final ObjectMapper mapper = new ObjectMapper();
private Map<String, Object> map;
public Params () {
this.map = new HashMap<String, Object>();
}
public Params (Params another) {
this.map = new HashMap<String, Object>();
this.map.putAll(anotherHolder.map);
}
public Params(String string) {
try {
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {
};
if (string == null) {
this.map = new HashMap<String, Object>();
} else {
this.map = mapper.readValue(string, typeRef);
}
} catch (IOException e) {
throw new PersistenceException(e);
}
}
public String toJSON() throws PersistenceException {
try {
return mapper.writeValueAsString(this.map);
} catch (IOException e) {
throw new PersistenceException(e);
}
}
public boolean containsKey(String key) {
return this.map.containsKey(key);
}
// Hash map methods
public Object get(String key) {
return this.map.get(key);
}
public Object put(String key, Object value) {
return this.map.put(key, value);
}
public void remove(String key) {
this.map.remove(key);
}
public Object size() {
return map.size();
}
}
HTH
If you are using JPA version 2.1 or higher you can go with this case.
Link Persist Json Object
public class HashMapConverter implements AttributeConverter<Map<String, Object>, String> {
#Override
public String convertToDatabaseColumn(Map<String, Object> customerInfo) {
String customerInfoJson = null;
try {
customerInfoJson = objectMapper.writeValueAsString(customerInfo);
} catch (final JsonProcessingException e) {
logger.error("JSON writing error", e);
}
return customerInfoJson;
}
#Override
public Map<String, Object> convertToEntityAttribute(String customerInfoJSON) {
Map<String, Object> customerInfo = null;
try {
customerInfo = objectMapper.readValue(customerInfoJSON,
new TypeReference<HashMap<String, Object>>() {});
} catch (final IOException e) {
logger.error("JSON reading error", e);
}
return customerInfo;
}
}
A standard JSON object would represent those attributes as a HashMap:
#Convert(converter = HashMapConverter.class)
private Map<String, Object> entityAttributes;