Parsing nested json received from an api to objects in spring boot - spring

I am creating a spring boot application which receives some JSON data from a 3rd party api. The JSON data has so many nested objects.I want to map them as Java objects. Below is the code I wrote for getting the api response.
public ResponseEntity<MovieSearchResultsDto> getMovies(String searchText, String countryCode) {
logger.info("GetMovies Service started");
String url = prepareUrl(searchText,countryCode);
HttpHeaders header = new HttpHeaders();
prepareHeader(header);
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API for movie info");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
MovieSearchResultsDto.class);
}catch (Exception e) {
logger.error("Exception occured while calling the API "+ e);
if(responseEntity.getStatusCodeValue() != 200) {
}
}
logger.info("GetMovies Service Ended");
return responseEntity;
}
And the JSON response looks like
{
"results": [
{
"id": ******,
"picture": "url",
"name": "Titanic",
"locations": [
{
"icon": "url",
"display_name": "Amazon Instant Video",
"name": "AmazonInstantVideoIVAGB",
"id": "***",
"url": "url"
}
],
"provider": "iva",
"weight": 0,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "url",
"id": "tt0046435"
},
"tmdb": {
"url": "url",
"id": "id"
},
"wiki_data": {
"url": "url",
"id": "id"
},
"iva": null,
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
}
] }
What I have done is , I created a class MovieSearchResultsDto and include a list as its data member with getters and setters.
private List<MoviesDto> results = new ArrayList<>();
And created MoviesDto class as below
public class MoviesDto {
private String id;
private String name;
private String picture;
#JsonInclude(value = Include.NON_EMPTY)
private List<MovieLocation> locations = new ArrayList<MovieLocation>();
#JsonInclude(value = Include.NON_EMPTY)
private List<ExternalIds> external_ids = new ArrayList<ExternalIds>();
public MoviesDto() {
}
//getters and setters
}
class MovieLocation{
private String icon;
private String id;
private String display_name;
private String name;
private String url;
public MovieLocation() {
}
//getters and setters
}
class ExternalIds{
private IdAndUrl imdb;
private IdAndUrl tmdb;
private IdAndUrl wiki_data;
public ExternalIds() {
}
//getters and setters
}
class IdAndUrl{
private String url;
private String id;
public IdAndUrl() {
}
//getters and setters
}
But it shows error while parsing.
Exception occured while calling the API org.springframework.web.client.RestClientException: Error while extracting response for type [class com.prebeesh1427.MovieNameServiceProvider.dto.MovieSearchResultsDto] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList<com.prebeesh1427.MovieNameServiceProvider.dto.ExternalIds>` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<com.prebeesh1427.MovieNameServiceProvider.dto.ExternalIds>` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1054] (through reference chain: com.prebeesh1427.MovieNameServiceProvider.dto.MovieSearchResultsDto["results"]->java.util.ArrayList[0]->com.prebeesh1427.MovieNameServiceProvider.dto.MoviesDto["external_ids"])
I am a newbie to this area. Kindly help me not just only to resolve this issue but to understand the concept of these parsing techniques too.
Thanks in advance

Related

One to Many save (POST) results in Bad Request (400) using Fetch for List<Entity>

I am trying to save Parent (One) and Children (Many) entities at the same time.
I took help from here and here.
I have an User Entity like below:
#Entity
#Table(name = "app_user")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class AppUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "salutation")
private String salutation;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "preference")
private String preference;
public AppUser(String salutation, String name, String email, String preference, List<Address> addressList,
List<Expertise> expertise) {
super();
this.salutation = salutation;
this.name = name;
this.email = email;
this.preference = preference;
this.addressList = addressList;
this.expertise = expertise;
}
#OneToMany(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "address_id")
private List<Address> addressList = new ArrayList<>();
#OneToMany(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "expertise_id")
private List<Expertise> expertise = new ArrayList<>();
My POST controller method.
#PostMapping("/appUsers")
public ResponseEntity<AppUser> createUser(#RequestBody AppUser appUser) {
try {
AppUser _appUser = appUserRepository.save(
new AppUser(appUser.getSalutation(), appUser.getName(), appUser.getEmail(),
appUser.getPreference(), appUser.getAddressList(),
appUser.getExpertise()));
return new ResponseEntity<>(_appUser, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
My pure JS (Fetch) snippet:
<script>
async function postDataToServer(postData) {
const baseURL = "http://localhost:8080/api";
try {
const res = await fetch(`${baseURL}/appUsers`, {
method: "post",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(postData),
});
if (!res.ok) {
const message = `An error has occured: ${res.status} - ${res.statusText}`;
throw new Error(message);
}
} catch (err) {
alert(err.message);
}
}
</script>
Using above, I can see the form data nicely forming up like below:
{
"salutation": "Mr.",
"name": "Ajay Kumar",
"email": "ajay#kumar.com",
"address_main": "1234 StreetName State 12345",
"address_1": "2345 StreetName State 23456",
"address_2": "3456 StreetName State 34567",
"preference": "Vegeterian",
"expertise": [
"java",
"springboot",
"javascript"
],
"secret": "1abc1234-1abc-4321-1234-1234abcd1234"
}
During submit if I don't select expertise, it all works find. i.e. the user gets saved but if I select expertise checkboxes I get a 400 bad request message at the browser console and JSON parse erroSTS console like this:
2022-02-25 11:02:53.009 WARN 25007 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.spring.boot.rocks.model.Expertise (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('java'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.spring.boot.rocks.model.Expertise (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('java') at [Source: (PushbackInputStream); line: 1, column: 234] (through reference chain: com.spring.boot.rocks.model.AppUser["expertise"]->java.util.ArrayList[0])]
I created a github project here if any more details are required.
Question: What I am missing? How do I convert expertise collection to List using pure JS only ? Or how do I handle expertise collection in controller?
Your form data is not in correct format. This should be like this:
{
"salutation": "Mr.",
"name": "Ajay Kumar",
"email": "ajay#kumar.com",
"address_main": "1234 StreetName State 12345",
"address_1": "2345 StreetName State 23456",
"address_2": "3456 StreetName State 34567",
"preference": "Vegeterian",
"expertise": [
{
"java",
"springboot",
"javascript"
}
],
"secret": "1abc1234-1abc-4321-1234-1234abcd1234"
}
Expertise and address in your parent class are lists, not normal objectType entity. If any of these two lists are not present, try to set them as emptyList before saving.

Cannot deserialize instance of POJO out of START_ARRAY token

I'm using merriam webster API and for some words, i get this error. I tried to see the difference between words and found that some of them are missing a property, in this case It's app-shortdef, which is a POJO class that i've created.
As I said, It only occurs on words that misses this property. Is there a way to just ignore these words?
Word that doens't work
"meta": {
"id": "car park",
"uuid": "c54c3476-f520-457e-97db-355b4fad1a0f",
"src": "learners",
"section": "alpha",
"target": {
"tuuid": "f675a703-73a4-4ba0-bab2-b516be9c9d5a",
"tsrc": "collegiate"
},
"stems": [
"car park",
"car parks"
],
"app-shortdef": [],
"offensive": false
},
Word that works
"meta": {
"id": "ball:2",
"uuid": "908a56c8-aa20-440d-bd0c-735d956ff1f4",
"src": "learners",
"section": "alpha",
"target": {
"tuuid": "be866f14-c794-4dee-bc3b-aee0002daaaa",
"tsrc": "collegiate"
},
"stems": [
"ball",
"balled",
"balling",
"balls"
],
"app-shortdef": {
"hw": "ball:2",
"fl": "verb",
"def": [
"{bc} to form (something) into a ball"
]
},
"offensive": false
},
As you can see, the difference is that app-shortdef is empty in the first one. Here is my POJO structure:
Dicionario
package com.ankitoword.entity;
public class Dicionario {
private Meta meta;
public Meta getMeta() {
return meta;
}
public void setMeta(Meta meta) {
this.meta = meta;
}
}
Meta
public class Meta {
private String id;
#JsonProperty(value="app-shortdef")
private AppShortDef appShortdef;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public AppShortDef getAppShortdef() {
return appShortdef;
}
public void setAppShortdef(AppShortDef appShortdef) {
this.appShortdef = appShortdef;
}
}
App
public class AppShortDef {
public String hw;
public String fl;
public String[] def;
public String getHw() {
String[] split = hw.split(":");
return split[0];
}
public void setHw(String hw) {
this.hw = hw;
}
public String getFl() {
return fl;
}
public void setFl(String fl) {
this.fl = fl;
}
public String[] getDef() {
return def;
}
public void setDef(String[] def) {
this.def = def;
}
}
Full Error
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Nov 26 19:24:58 BRT 2020
There was an unexpected error (type=Internal Server Error, status=500).
JSON decoding error: Cannot deserialize instance of `com.ankitoword.entity.AppShortDef` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.ankitoword.entity.AppShortDef` out of START_ARRAY token at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.ankitoword.entity.Dicionario["meta"]->com.ankitoword.entity.Meta["app-shortdef"])
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize instance of `com.ankitoword.entity.AppShortDef` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.ankitoword.entity.AppShortDef` out of START_ARRAY token
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.ankitoword.entity.Dicionario["meta"]->com.ankitoword.entity.Meta["app-shortdef"])
at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:215)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
Service Class
The error occurs on the collectList() line.
public List<Dicionario> getWords(String palavra) {
// OMono permite tratar o retorno quando a requisição finalizar sem bloquear o método.
Flux<Dicionario> fluxDicionario = this.webClient
.get()
.uri(builder -> builder.path("/"+palavra).queryParam("key", APIKey).build())
.retrieve() //Retorna o response-spec
.bodyToFlux(Dicionario.class);
List<Dicionario> dicionarios = new ArrayList<>();
dicionarios = fluxDicionario.collectList().block();
return dicionarios;
}
#Edit
I've fixed this problem accepting a Object as a parameter, then checking if It's a array list, meaning It's the empty array and can't be converted.
public void setAppShortDef(Object object) {
if (object instanceof ArrayList){
//
} else {
ObjectMapper mapper = new ObjectMapper();
AppShortDef appShortDef = mapper.convertValue(object, AppShortDef.class);
this.appShortDef = appShortDef;
}
}
I Have another problem now, I can get a response that doesn't contain any of my properties. Ex:
[
"testified",
"the last\/final word",
"testify for",
"Lakewood",
"foreword",
"headword",
"lakewood",
"sent word",
"swearword",
"testament",
"testamentary",
"testaments",
"tested",
"tester",
"testers",
"testier",
"testosterone",
"the f-word",
"the final word",
"true to her word"
]

Cannot deserialize instance

I have a problem. I am using Spring Boot and sqlite3 DB. I tried to send data to the DB.
When I sent data to the DB I have this error:
{
"timestamp": "2019-02-12T12:39:40.413+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize instance of `com.dar.darkozmetika.models.CategoryModel` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.dar.darkozmetika.models.CategoryModel` out of START_ARRAY token\n at [Source: (PushbackInputStream); line: 1, column: 1]",
"path": "/api/cateogry/dar"
}
This is my controller:
RestController
#RequestMapping("api/cateogry/dar")
public class CategoryController {
#Autowired
private CategoryRepository categoryRepository;
#GetMapping
private List<CategoryModel> getAllCategory (){
System.out.println("sadad");
System.out.println("sadad" + this.categoryRepository.findAll());
return this.categoryRepository.findAll();
}
#PostMapping
#ResponseStatus(HttpStatus.OK)
public void create(#RequestBody CategoryModel bike) {
categoryRepository.save(bike);
}
#GetMapping("/{id}")
public CategoryModel getSpecificCategory(#PathVariable("id") long id) {
return null;//categoryRepository.getOne(id);
}
}
This is my model:
#Entity
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class CategoryModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String categoryName;
private String categoryDescription;
private String imagePath;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCategoryName() {
return categoryName;
}
I sent this data from Postman:
[
{
"id": 2,
"categoryName": "dsds",
"categoryDescription": "sdsd",
"imagePath": "Jsdsds"
}
]
Very interesting, I can get data from the DB without problems. This is return form my DB.
[
{
"id": 1,
"categoryName": "jeff#bikes.com",
"categoryDescription": "Globo MTB 29 Full Suspension",
"imagePath": "Jeff Miller"
}
]
Your request is not ok, you are sending an array of CategoryModel and the POST api/cateogry/dar receives just a CategoryModel. You should send just:
{
"id": 2,
"categoryName": "dsds",
"categoryDescription": "sdsd",
"imagePath": "Jsdsds"
}

Nested Group with Spring MongoDB

I need to generate a result with the number of alerts of each level for each user.
A structure similar to the following:
{
"identitity": "59e3b9dc5a3254691f327b67",
"alerts": [
{
"level": "INFO",
"count": "3"
},
{
"level": "ERROR",
"count": "10"
}
]
}
The alert entitity has the following structure:
#Document(collection = AlertEntity.COLLECTION_NAME)
public class AlertEntity {
public final static String COLLECTION_NAME = "alerts";
#Id
private ObjectId id;
#Field
private AlertLevelEnum level = AlertLevelEnum.INFO;
#Field("title")
private String title;
#Field("payload")
private String payload;
#Field("create_at")
private Date createAt = new Date();
#Field("delivered_at")
private Date deliveredAt;
#Field("delivery_mode")
private AlertDeliveryModeEnum deliveryMode =
AlertDeliveryModeEnum.PUSH_NOTIFICATION;
#Field("parent")
#DBRef
private ParentEntity parent;
#Field("son")
#DBRef
private SonEntity son;
private Boolean delivered = Boolean.FALSE;
}
I have implemented the following method tried to project the result in a nested way. But the "Identity" field is always null and the "alerts" field is a empty collection.
#Override
public List<AlertsBySonDTO> getAlertsBySon(List<String> sonIds) {
TypedAggregation<AlertEntity> alertsAggregation =
Aggregation.newAggregation(AlertEntity.class,
Aggregation.group("son.id", "level").count().as("count"),
Aggregation.project().and("son.id").as("id")
.and("alerts").nested(
bind("level", "level").and("count")));
// Aggregation.match(Criteria.where("_id").in(sonIds)
AggregationResults<AlertsBySonDTO> results = mongoTemplate.
aggregate(alertsAggregation, AlertsBySonDTO.class);
List<AlertsBySonDTO> alertsBySonResultsList = results.getMappedResults();
return alertsBySonResultsList;
}
The result I get is the following:
{
"response_code_name": "ALERTS_BY_SON",
"response_status": "SUCCESS",
"response_http_status": "OK",
"response_info_url": "http://yourAppUrlToDocumentedApiCodes.com/api/support/710",
"response_data": [
{
"identity": null,
"alerts": []
},
{
"identity": null,
"alerts": []
}
],
"response_code": 710
}
The result DTO is as follows:
public final class AlertsBySonDTO implements Serializable {
private static final long serialVersionUID = 1L;
#JsonProperty("identity")
private String id;
#JsonProperty("alerts")
private ArrayList<Map<String, String>> alerts;
public AlertsBySonDTO() {
super();
}
public AlertsBySonDTO(String id, ArrayList<Map<String, String>> alerts) {
super();
this.id = id;
this.alerts = alerts;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public ArrayList<Map<String, String>> getAlerts() {
return alerts;
}
public void setAlerts(ArrayList<Map<String, String>> alerts) {
this.alerts = alerts;
}
}
What needs to be done to project the result in a nested way?
Thanks in advance
In aggregation framework there is an $unwind operator which will basically transform your one element collection with nested array of two elements to two separate documents with one element from this array. So you'll get:
{
"identitity": "59e3b9dc5a3254691f327b67",
"alerts": {
"level": "INFO",
"count": "3"
}
}
{
"identitity": "59e3b9dc5a3254691f327b67",
"alerts": {
"level": "ERROR",
"count": "10"
}
}
And this is where you can start your group by with count. Should be working fine.

Get data as a JSON format in spring boot

I want to build a request endepoints using spring boot: I have to consume restful api and convert that into another rest endpoint.
I have a json Response on www.exampleapiurl.com/details
[{
"name": "age",
"value": "Child"
},
{
"name": "recommendable",
"value": true
},
{
"name": "supported",
"value": yes
},
]
[{
"name": "age",
"value": "Adult"
},
{
"name": "recommendable",
"value": true
},
{
"name": "supported",
"value": no
},
]
I want the response to be:
[{
"age": "Child"
},
{
"recommendable": true
},
{
"supported": "yes"
},
]
[{
"age": "Adult"
},
{
"recommendable": true
},
{
"supported": "no"
},
]
For this I have a attribute class with getter and setter:
Attributes.class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Attributes {
private String age;
private boolean recommendable;
private String supported;
getter and setter for these:
}
This is my service.java class
#Service
public class CService {
private static RestTemplate restTemplate;
public String url;
#Autowired
public CService(String url) {
this.url = url;
}
public Attributes getAttributes() {
HttpHeaders headers= new HttpHeaders();
headers.add("Authorization", "some value");
HttpEntity<String> request = new HttpEntity<String>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, request, Attributes.class);
return response.getBody();
}
}
And this is my controller.class
#Controller
public class CController {
private CService cService;
#Autowired
public CController(CService cService) {
this.cService = cService;
}
#RequestMapping(value="/example")
#ResponseBody
public Attributes getCAttributes() {
return cService.getAttributes(); }
}
The Authorization is successful but,
I am not getting any response for now
What you can do is create a model class to recive the response from example API
as follows.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Details{
private String name;
private String value;
#JsonProperty("value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#JsonProperty("name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Then change your invoking RestTemplate code as follows
Details[] details = null;
//Keeps the example API's data in array.
ResponseEntity<Details[]> response = restTemplate.exchange(url, HttpMethod.GET, request, Details[].class);
details = response.getBody();
//Next step is to process this array and send the response back to your client
List<Attributes> attributes = new ArrayList<Attributes>();
Attributes attr = null;
for(Details detail : details) {
attr = new Attributes();
//set the values here
}
//returns the attributes here
attributes.toArray(new Attributes[attributes.size()]);

Resources