error in parsing JSON using GSON with retrofit API - gson

this is POJO or model class that I am using, I'm able to get the response from server but it is throwing SyntaxException and MalformedJSONException
//Model class:
public class AppUser {
public Integer userID;
public String username;
public String password;
public String emailID;
public String accessToken;
}
//JSON Retrieved:
{
"userID": "123456",
"username": "rkumar",
"password": "rohitk",
"emailID": "rohitk#gmail.com",
"accessToken": "1weErtYo90ds8i9"
}
//JSON Deserializer
public class AppUserDeserializer implements JsonDeserializer<AppUser> {
#Override
public AppUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Log.d("Json element",json.toString());
return new Gson().fromJson(json, AppUser.class);
}
}
//Rest Client Constructor
public RestClient()
{
Gson gson = new GsonBuilder()
.registerTypeAdapter(AppUser.class, new AppUserDeserializer())
.create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(baseURL)
.setConverter(new GsonConverter(gson))
.build();
signInService = restAdapter.create(SignInService.class);
}

Related

no String-argument constructor/factory method to deserialize from String value with spring boot client

I am using spring boot application with frontend (spring boot application using thymeleaf) and backend (spring boot REST application ) are separated using REST api. The frontend uses HttpClient to send request to backend. Whenever I try to update an object the HttpClient creates an error for json parsing. The request is not accepted by the backend (ProcessDTORequest object ) with error as follows.
The exception is as follows:
{"message":"JSON parse error: Cannot construct instance of `com.app.dataaccess.entity.Process` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('68d22e4d-7116-4130-aa06-9ba120aadc66'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.app.dataaccess.entity.Process` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('68d22e4d-7116-4130-aa06-9ba120aadc66')\n at [Source: (PushbackInputStream); line: 1, column: 10310] (through reference chain: com.app.ui.dto.request.ProcessDTORequest[\"answeredQuestionnaires\"]->java.util.HashSet[0]->com.app.dataaccess.entity.AnsweredQuestionnaire[\"process\"])","httpStatus":"INTERNAL_SERVER_ERROR","timeStamp":"2022-11-04T08:44:35.9108286Z"}
HttpClient method for post request is as follows:
public String executePost(
final String url, final Object payLoad, final Map<String, String> headers,
final Map<String, String> params) throws Exception {
final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// Add query strings to URL
URIBuilder builder = new URIBuilder(url);
for (final Map.Entry<String, String> elm : params.entrySet()) {
builder = builder.setParameter(elm.getKey(), elm.getValue());
}
// can change for HttpPut, HttpPost, HttpPatch
final HttpPost request = new HttpPost(builder.build());
// Add headers from input map
for (final Map.Entry<String, String> elm : headers.entrySet()) {
request.addHeader(elm.getKey(), elm.getValue());
}
request.setHeader("Accept", "application/json");
request.setHeader("Content-type", "application/json");
// Send Json String as body, can also send UrlEncodedFormEntity
final StringEntity entity = new StringEntity(objectMapper.writeValueAsString(payLoad));
request.setEntity(entity);
try {
final CloseableHttpResponse response = httpClient.execute(request);
System.out.println("Return response status code: "+response.getStatusLine().getStatusCode());
System.out.println("Return response status code: "+response.getStatusLine());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// Read response string using EntityUtils class of Apache http client library
// Serialize json string into map or any other object
return EntityUtils.toString(response.getEntity());
} else {
throw new Exception(EntityUtils.toString(response.getEntity()));
// throw new Exception(String.format("Response status code was and response was ",
// response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity())));
}
} catch (final ClientProtocolException e) {
throw new Exception("Client protocol Exception occurred while executing request", e);
} catch (final Exception e) {
System.out.println(e);
throw new Exception(e);
}
}
I used the configuration for object mapper as follows:
#Configuration
public class AppConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
objectMapper.registerModule(new JavaTimeModule());
return objectMapper; }
}
Process.java (this is used for serializing/deserializing)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
public class Process {
private UUID processId;
private List<User> users = new ArrayList<>();
private List<UnitType> units = new ArrayList<>();
private String furtherComment;
private List<AnsweredQuestionnaire> answeredQuestionnaires = new ArrayList<>()
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Process)) return false;
Process process = (Process) o;
return getProcessId().equals(process.getProcessId());
}
#Override
public int hashCode() {
return Objects.hash(getProcessId());
}
}
The json from the server is like the following
{
"#id": "bba35e58-5d4b-44ce-9a5a-486f55f79af7",
"processId": "21ef7f9d-4fcc-417c-96e8-4327206d2592",
"users": [
{
"#id": "69d2f392-8213-4f34-9cb5-f0c403170787",
"userId": "5a17ec5f-c20a-4873-93af-bf69fad4eb26",
"roles": [
{
"roleId": "f6ad33a7-9d03-4260-81c2-a4a4c791e30a",
"users": []
}
],
"processes": []
}
],
"units": [
{
"unitTypeId": "c784d197-1dc7-446e-b3e5-6468a7954878",
"unit": {
"unitId": "aba76d05-e2ea-4b5a-828b-349966595258"
},
"isResponsibleUnit": true
}
],
"furtherComment": "",
"answeredQuestionnaires": [
{
"#id": "7ca1af09-eefd-4c56-9587-581858fbbc57"
}
]
}
The relation between the entities Process, AnsweredQuestionnaire and User is as follows:
Between Process and AnsweredQuestionnaire (One-to-many) respectively.
Between Process and User (many-to-many).
Between Process and UnitType (one-to-many) respectively.
AnsweredQuestionnaire.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class AnsweredQuestionnaire {
private UUID answeredQuestionnaireId;
private Questionnaire questionnaire;
private Process process;
public void addProcessToAnsweredQuestionnaire(Process process){
//remove old association
if(this.process != null){
this.process.getAnsweredQuestionnaires().remove(this);
}
this.process = process;
//add new association
if(process != null){
this.process.getAnsweredQuestionnaires().add(this);
}
}
}
User.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class User {
private UUID userId;
private String firstName;
private String lastName;
private String phoneNumber;
private String email;
private List<Role> roles = new ArrayList<>();
private List<Process> processes = new ArrayList<>();
public void addProcessToUser(Process process){
this.processes.add(process);
process.getUsers().add(this);
}
public void removeProcessFromUser(Process process){
this.processes.remove(process);
process.getUsers().remove(this);
}
}
ProcessDTORequest.java (this class is on the backend accepting the request from the frontend)
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class ProcessDTORequest {
private UUID processId;
private Set<User> users = new HashSet<>();
private Set<AnsweredQuestionnaire> answeredQuestionnaires = new HashSet<>();
private Set<UnitType> units = new HashSet<>();
}
UnitType.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class UnitType {
private UUID unitTypeId;
private Unit unit;
private Boolean isResponsibleUnit = false;
}

Converter works for RequestParameter but not for RequestBody field

I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}
Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}

Handler Goblal Exceptions Spring - add data when sending exception

I have a doubt about how to pass more data to throw an exception, I want to pass more data at the time of launching it, to put that data in the service response ..
I have an exception handler class labeled #ControllerAdvice in spring, but I don't know the best way to pass the data.
This is the code I have
throw new OcspException("Exception OCSP");
public class OcspException extends RuntimeException {
private static final long serialVersionUID = 1L;
public OcspException(String businessMessage) {
super(businessMessage);
}
public OcspException(String businessMessage, Throwable throwable) {
super(businessMessage, throwable);
}
}
#ExceptionHandler(OcspException.class)
public ResponseEntity<Object> exception(OcspException exception,HttpServletRequest request) {
ResponseException response = new ResponseException();
response.setCode("404");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
I have the idea to do it, but I don't know if it is a good practice ... in the OcspException class to create attributes with their setter and getters, and create the constructor that receives this data, to then extract the data in exception controller
throw new OcspException("Exception OCSP","Hello");
public class OcspException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String m;
public OcspException(String businessMessage) {
super(businessMessage);
}
public OcspException(String businessMessage, Throwable throwable) {
super(businessMessage, throwable);
}
public OcspException(String businessMessage, String message) {
super(businessMessage);
setM(message);
}
public String getM() {
return m;
}
public void setM(String m) {
this.m = m;
}
}
#ExceptionHandler(OcspException.class)
public ResponseEntity<Object> exception(OcspException exception,HttpServletRequest request) {
ResponseException response = new ResponseException();
response.setCode("404");
response.setDetails(exception.getM() );
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
Try making an model called ErrorDetails which will hold a timestamp, message, and details.
It may look like this:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class ErrorDetails {
private LocalDateTime timeStamp;
private String message;
private String details;
}
Here's a sample of what my custom exceptions usually look like:
#Data
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class OrderNotFoundException extends RuntimeException {
private final String message;
public OrderNotFoundException(String message) {
super(message);
this.message = message;
}
}
Then for the #ExceptionHandler:
#ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorDetails>
orderNotFoundException(OrderNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = ErrorDetails.builder()
.timeStamp(LocalDateTime.now())
.message(ex.getMessage())
.details(request.getDescription(false))
.build();
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
The error response for an order not found ends up being this:
{
"timeStamp": "2019-10-07T21:31:37.186",
"message": "Order with id 70 was not found.",
"details": "uri=/api/v1/order"
}
This way you can add whatever extra details in the ErrorDetails object. I hope that helps!

How to properly implement adding a new record into Elasticsearch using Spring Boot?

Getting started with Spring Boot / Spring Data / Elasticsearch application.
ES -> 6.1
Have a simple repository:
public interface BusinessMetadataRepository extends ElasticsearchRepository<BusinessMetadata, Long> {
List<BusinessMetadata> findByName(String name);
List<BusinessMetadata> findById(Long id);
}
And a Business Object:
import org.springframework.data.elasticsearch.annotations.Document;
#Document(indexName = "bsn", type = "mtd", shards = 1)
public class BusinessMetadata {
private Long id;
private String name;
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BusinessMetadata(Long id, String name) {
this.id = id;
this.name = name;
}
public BusinessMetadata() {
}
}
Elastic Configuration:
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.discover.harmony.elastic.repository")
public class ElasticConfiguration {
#Bean
public NodeBuilder nodeBuilder() {
return new NodeBuilder();
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws IOException {
File tmpDir = File.createTempFile("elastic", Long.toString(System.nanoTime()));
System.out.println("Temp directory: " + tmpDir.getAbsolutePath());
Settings.Builder elasticsearchSettings =
Settings.settingsBuilder()
.put("http.enabled", "true") // 1
.put("index.number_of_shards", "1")
.put("path.data", new File(tmpDir, "data").getAbsolutePath()) // 2
.put("path.logs", new File(tmpDir, "logs").getAbsolutePath()) // 2
.put("path.work", new File(tmpDir, "work").getAbsolutePath()) // 2
.put("path.home", tmpDir); // 3
return new ElasticsearchTemplate(nodeBuilder()
.local(true)
.settings(elasticsearchSettings.build())
.node()
.client());
}
}
My Rest Controller for doing search works fine:
#RestController
#RequestMapping("/rest/search")
public class SearchResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#GetMapping(value = "/name/{text}")
public List<BusinessMetadata> searchName(#PathVariable final String text) {
return businessMetadataRepository.findByName(text);
}
#GetMapping(value = "/all")
public List<BusinessMetadata> searchAll() {
List<BusinessMetadata> businessMetadataList = new ArrayList<>();
Iterable<BusinessMetadata> businessMetadata = businessMetadataRepository.findAll();
businessMetadata.forEach(businessMetadataList::add);
return businessMetadataList;
}
}
My Rest Controller for doing save:
#RestController
#RequestMapping("/rest/save")
public class SaveResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#GetMapping(value = "/name/{text}")
public void Save(String text) {
businessMetadataRepository.save(new BusinessMetadata((long)99, text));
}
}
When I test the save using Postman, I get this error:
{
"timestamp": 1514325625996,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/rest/save/name/new-1"
}
What changes do I need to make in order to properly configure this project to support inserting new documents?
Based on the comment from AntJavaDev, I have modified my controller in the following way:
#RestController
#RequestMapping("/rest/save")
public class SaveResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#PostMapping("/name/{text}")
public void Save(#PathVariable String text) {
BusinessMetadata mtd = businessMetadataRepository.save(new BusinessMetadata(text));
}
}
The 2 key changes are: replace #GetMapping with #PostMapping, and include #PathVariable as a parameter qualifier.
Now it works as expected

How to de-serialize POJO contains HashTable?

I have pojo like this:
public class Test implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String hash;
private java.util.Hashtable<Integer, Long> myTempTable;
public java.util.Hashtable<Integer, Long> getMyTempTable() {
return this.myTempTable;
}
public void setMyTempTable(java.util.Hashtable<Integer, Long> myTempTable) { this.myTempTable = myTempTable; }
//And some few variables
}
In response I get this POJO in JSON format but while converting this JSON to "Test" java object like this.
gson.fromJson(tempString, Test.class);
It is giving error as
java.lang.IllegalArgumentException: Can not set java.util.Hashtable field <package_name>.Temp.myTempTable to java.util.LinkedHashMap
Why GSON is converting HashTable to LinkedHashMap?
And does this error means?
UPDATE: JSON File as
{
"hash": "abc",
"myTempTable": {
"1": 30065833999,
"2": 34364325903,
"3": 536872959
}
}
For converting an Object to JSON String.
public static <T> String convertObjectToStringJson(T someObject, Type type) {
Gson mGson = new Gson();
String strJson = mGson.toJson(someObject, type);
return strJson;
}
For converting a JSON String to an Object.
public static <T> T getObjectFromJson(String json, Type type) {
Gson mGson = new Gson();
if (json != null) {
if (json.isEmpty()) {
return null;
}
}
return mGson.fromJson(json, type);
}
where
Type is type of your Object.
ex:
for object:
new TypeToken<YOUR_POJO>(){}.getType();
for list:
new TypeToken<List<YOUR_POJO>>(){}.getType();

Resources