restTemplate.getForObject doesn't read json array information - spring-boot

I'm in trouble to read a json data that have objects and arrays mixed in his body.
I can read and convert all objects kinds but the forecast node it is an array and I always receive an null as response.
See more details bellow:
My web API give me json response:
{
"location": {
"name": "Brasilia"
},
"current": {
"last_updated": "2019-01-11 19:00",
"condition": {
"text": "Patchy rain possible"
}
},
"forecast": {
"forecastday": [
{
"date": "2019-01-11",
"day": {
"avgtemp_c": 21.4
}
},
{
"date": "2019-01-12",
"day": {
"avgtemp_c": 22.0
}
}
]
}
}
I'm using restTemplate to get the data:
ApiResponse apiResponse = restTemplate.getForObject(uri, ApiResponse.class);
And here is my ApiResponse response estructure:
#JsonIgnoreProperties(ignoreUnknown = true)
public class ApiResponse {
private Location location;
private Current current;
private Forecast forecast;
/*constructors, getters and setters mmited */
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Location {
private String name;
private String region;
private String country;
private Float lat;
private Float lon;
private String localtime;
/*constructors, getters and setters mmited */
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Current {
private String last_updated;
private Float temp_c;
private Float precip_mm;
private Condition condition;
/*constructors, getters and setters mmited */
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Forecast {
public List<Forecastday> forecastday;
/*constructors, getters and setters mmited */
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Forecastday {
private String date;
private Day day;
/*constructors, getters and setters mmited */
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Day {
private Float avgtemp_c;
private Float totalprecip_mm;
private List<Condition> condition;
/*constructors, getters and setters mmited */
}
I guess that i am doing the class mapping in a wrong way but i can't see where is the problem.
Can any one help me?

Hey guys i've found the error.
My mapping was really wrong!
In my last class: private List<Condition> condition; isn't a list but a simple object: private Condition condition; and because this i was receiving a Cannot deserialize instance ofjava.util.ArrayListout of START_OBJECT token
This another thread help me to see where i did wrong.

Related

Spring Boot Java map Entity to DTO: array literal (strings) INSTEAD of array of objects

sample get request: http://localhost:3000/contact/1
What I got:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": [
{
"emailaddress": "email#gmail.com"
},
{
"emailaddress": "email#g.c"
}
]
}
What I want:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": ["email#gmail.com","email#g.c"]
}
The code below:
PersonDto
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<EmailAddressDto> emailaddresses;
//getters setters
}
EmailAddressDto
public class EmailAddressDto {
private String emailaddress;
//getters and setters
}
the Service class
public PersonDto getPerson(Long personId) { //this is the method inside the class
Optional<PersonEntity> p = peopleRepository.findById(personId);
var dto = modelMapper.map(p.get(), PersonDto.class);
return dto;
}
I also have a PersonEntity class mapped one-to-many to an EmailAddressesEntity class.
I'm really new to spring/java - I couldn't figure out how to get the JSON structure I want.
You can just annotate emailaddress field of EmailAddressDto with #JsonValue and leave everything as is.
public class EmailAddressDto {
#JsonValue
private String emailaddress;
//getters and setters
}
Using the above the output of a sample:
PersonDto personDto = new PersonDto();
personDto.setId(1L);
personDto.setFirstname("John");
personDto.setLastname("Doe");
personDto.setEmailaddresses(Arrays.asList(new EmailAddressDto("john#doe.com"), new EmailAddressDto("foo#bar.com")));
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(personDto);
System.out.println(json);
is:
{"id":1,"firstname":"John","lastname":"Doe","emailaddresses":["john#doe.com","foo#bar.com"]}
I'd suggest that you use a List of Strings instead of a List of EmailAddressDto's.
Following reasons:
Since you only have one attribute in your Dto, you can easily just directly use a List of Strings instead.
You get the second JSON-Layout as a response to your GET-Request.
When using variant number 1 (with the List of EmailAddressDto), you will achieve a JSON-Response with multiple objects for your different E-Mail addresses.
Otherwise when you use variant number 2 (with the List of String), you will achieve a JSON-Response which looks like what you want to have.
So don't forget to change your entities aswell.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailAddresses;
//getters setters
}
If you can change your PersonDto that would be the easiest and cleanest way to do it.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailaddresses;
//getters setters
}
While mapping your entities you would need to map EmailAddressesEntity to a String representing it (emailaddress).
If this is not possible you will need a custom converter for EmailAddressDto as follows:
public class ListEmailAddressDtoConverter extends StdConverter<List<EmailAddressDto>, List<String>> {
#Override
public List<String> convert(List<EmailAddressDto> emailAddresses) {
return emailAddresses.stream().map(EmailAddressDto::getEmailaddress).collect(Collectors.toList());
}
}
Then you need to tell Jackson to use it:
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
#JsonSerialize(converter = ListEmailAddressDtoConverter.class)
private List<EmailAddressDto> emailaddresses;
//getters setters
}

SpringBoot - 400 bad request after adding List of objects to input

I have a simple ReactJS/SpringBoot application which generates XML files used for software licenses.
This has been working fine, but now I'm trying to add an "attributes" table which has a many-to-one relationship with the license table. It will keep track of attributes specified in the front end that will be set to true on the license.
I've used these URLs as a guide for the backend (video and related code):
https://www.youtube.com/watch?v=8qhaDBCJh6I
https://github.com/Java-Techie-jt/spring-data-jpa-one2many-join-example
However, I'm getting a 400 error both on the update and the addition of a license when I try to use the updated code.
The front end seems to be working correctly.
Edit: looks like this is the culprit; although I haven't figured out why, yet.
Could not resolve parameter [0] in org.springframework.http.ResponseEntity<com.license.gen.app.model.License> com.license.gen.app.web.LicenseController.updateLicense(com.license.gen.app.model.License): JSON parse error: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS')
/endEdit
It's producing a JSON object with the attributes added as an array (e.g. at the end of the following object):
{
"id": 861,
"fullName": "johnsmith#abc.com",
"contact": "",
"requester": "johnsmith#abc.com",
"tag": "",
"company": "ACME",
"companyId": "ABC",
"template": "AN_4_2",
"product": "Analytics",
"expiration": "2022-04-15",
"macs": "11-11-11-11-11-11",
"typeId": "555",
"family": "FACILITY",
"systems": "2",
"licenseFilename": "license_johnsmith#abc.com.xml",
"url": "https://test-licenses.s3.amazonaws.com/license_johnsmith%40abc.com.xml",
"dateCreated": "2021-04-09T02:43:39.000+0000",
"dateUpdated": "2021-04-09T02:43:39.000+0000",
"user": {
"id": "00u560lmjop5poy624x6",
"name": "myname",
"email": "myname#gmail.com"
},
"attributes": [
"CONFIG_MSC_PARAMETERS",
"REPORTING"
]
}
Here is the updated License entity, with attributes added as a one-to-many List:
#EqualsAndHashCode
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table(name = "licenses")
public class License {
#Id
#GeneratedValue
private Long id;
#NonNull
private String fullName;
private String contact;
private String requester;
private String tag;
private String company;
private String companyId;
private String template;
private String product;
private String expiration;
private String macs;
private String typeId;
private String family;
private String systems;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
#OneToMany(targetEntity = Attribute.class,cascade = CascadeType.ALL)
#JoinColumn(name ="license_fk",referencedColumnName = "id")
private List<Attribute> attributes;
// getters, setters
...
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<Attribute> getAttributes() {
return attributes;
}
public void setAttributes(List<Attribute> attributes) {
this.attributes = attributes;
}
}
License Repository (no change):
package com.license.gen.app.model;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface LicenseRepository extends JpaRepository<License, Long>
{
License findByFullName(String fullName);
List<License> findAllByUserId(String id);
Page<License> findAll(Pageable pageable);
#Query("SELECT l FROM License l " +
"WHERE l.company LIKE %:company% " +
"OR l.macs LIKE %:macs% " +
"OR l.requester LIKE %:requester% " +
"OR l.tag LIKE %:tag% " +
"OR l.fullName LIKE %:fullName% " +
"OR l.template LIKE %:template% " +
"OR l.expiration LIKE %:expiration% " +
"OR l.family LIKE %:family% " +
"OR l.licenseFilename LIKE %:filename% " +
"OR l.product LIKE %:product%"
)
List<License> findBySearchString(
#Param("company") String company,
#Param("macs") String macs,
#Param("requester") String requester,
#Param("tag") String tag,
#Param("fullName") String fullName,
#Param("template") String template,
#Param("expiration") String expiration,
#Param("family") String family,
#Param("filename") String filename,
#Param("product") String product);
#Query("SELECT l FROM License l " +
"WHERE l.macs LIKE %:macs%"
)
List<License> findByMacs(
#Param("macs") String macs);
#Query("SELECT l FROM License l " +
"WHERE l.fullName LIKE %:fullName%"
)
List<License> findMatchesByFullName(
#Param("fullName") String fullName);
}
Attribute Entity (new):
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Attribute {
#Id
private Long id;
#NonNull
private String attribute;
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated = new Date();
#ManyToOne(cascade = CascadeType.PERSIST)
private License license;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
}
Attribute Repository (new):
public interface AttributeRepository extends JpaRepository<User, Long> {
}
And the License Controller:
#RestController
#RequestMapping("/api")
class LicenseController {
private final Logger log = LoggerFactory.getLogger(LicenseController.class);
private LicenseRepository licenseRepository;
private UserRepository userRepository;
private AttributeRepository attributeRepository;
public static String bucket;
public LicenseController(LicenseRepository licenseRepository,
UserRepository userRepository,
AttributeRepository attributeRepository) {
this.licenseRepository = licenseRepository;
this.userRepository = userRepository;
this.attributeRepository = attributeRepository;
}
.....
#PostMapping("/license")
ResponseEntity<LicensePojo> createLicense(#Valid #RequestBody License license,
#AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {
log.info("Request to create license: {}", license);
Map<String, Object> details = principal.getAttributes();
String userId = details.get("sub").toString();
// check to see if user already exists
Optional<User> user = userRepository.findById(userId);
license.setUser(user.orElse(new User(userId,
details.get("name").toString(), details.get("email").toString())));
if(license.getLicenseFilename() == null){
license.setLicenseFilename("");
}
License result = licenseRepository.save(license);
User myUser = license.getUser();
// Generate the license
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
result.setLicenseFilename(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
String origTypeId = String.valueOf(result.getTypeId());
String origId = String.valueOf(result.getId());
if ((origTypeId == null) || origTypeId.equalsIgnoreCase("")){
result.setTypeId(origId);
}
result = licenseRepository.save(result);
return ResponseEntity.created(new URI("/api/license/" + result.getId()))
.body(licensePojo);
}
#PutMapping("/license/{id}")
ResponseEntity<License> updateLicense(#Valid #RequestBody License license) {
List<Attribute> attributes = license.getAttributes();
License result = licenseRepository.save(license);
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
result.setLicenseFilename(fileName);
return ResponseEntity.ok().body(result);
}
...
}
As far as I can see, there are no error messages being generated. The IDE is showing the AttributeRepository isn't being used in the controller, but they may be because it's part of the underlying SpringData JPA code to implement it.
Any ideas what the problem might be?

spring boot ignore field dynamically jpa

I am using Spring Boot REST Web Services and Angular 5 as a frontend, well I have a model class for hibernating like this :
#Entity
public class Title {
private Integer id;
private String name;
private Date releaseDate;
private Time runtime;
private String storyline;
private String picture;
private String rated;
private String type;
private Double rating;
private Integer numberOfVotes;
private Timestamp inserted;
private Set<Genre> genres = new HashSet<>();
private List<TitleCelebrity> titleCelebrities;
private List<TitleMedia> titleMedia;
// Basic getters and setter
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "title_genre", joinColumns = { #JoinColumn(name = "title_id") }, inverseJoinColumns = { #JoinColumn(name = "genre_id") })
public Set<Genre> getGenres() {
return genres;
}
public void setGenres(Set<Genre> genres) {
this.genres = genres;
}
#OneToMany(mappedBy = "title", cascade = CascadeType.ALL)
public List<TitleCelebrity> getTitleCelebrities() {
return titleCelebrities;
}
public void setTitleCelebrities(List<TitleCelebrity> titleCelebrities) {
this.titleCelebrities = titleCelebrities;
}
#OneToMany(mappedBy = "title", cascade = CascadeType.ALL)
public List<TitleMedia> getTitleMedia() {
return titleMedia;
}
public void setTitleMedia(List<TitleMedia> titleMedia) {
this.titleMedia = titleMedia;
}
}
And here's my REST controller
#RestController
#RequestMapping("titles")
#CrossOrigin(origins = {"http://localhost:4200"})
public class TitleController {
private TitleService titleService;
#Autowired
public void setTitleService(TitleService titleService) {
this.titleService = titleService;
}
// Api to get all the movies ordered by release date
#GetMapping("movies")
public List<Title> getAllMoviesOrderByReleaseDateDesc() {
return this.titleService.findByTypeOrderByReleaseDateDesc("movie");
}
#GetMapping("movies/{id}")
public Title findById(#PathVariable Integer id) {
return this.titleService.findById(id);
}
}
What I want is when I make a request to the first method '/movies' i don't want the collection of Telemedia, but if I make a request to the second method '/movies/id' i want the collection of Telemedia.
of course, the annotation #JsonIgnore will ignore the collection whatever the request is.
It may be better to create two models in this case; one to represent the first response and another to represent the second response.
You could also set the collection to null in your second request before sending it back.
You cannot accomplish this with #JsonIgnore alone as you cannot perform conditional logic in annotations.

Sprint Date Rest successful, but no data

Entity
#Data
#Accessors(chain = true, fluent = true)
#Entity
#Table(name = "T_NOTE")
#Access(AccessType.FIELD)
public class Note implements Serializable
{
#Id
#GeneratedValue
private Long id;
private Date date;
#Column(length = 2000)
private String content;
private String title;
private String weather;
}
Repository
#RepositoryRestResource(collectionResourceRel = "note", path = "note")
public interface NoteRepository extends AbstractRepository<Note, Long>
{
}
GET http://localhost:8080/note/2
{
"_links": {
"self": {
"href": "http://localhost:8080/note/2"
}
}
}
No entity field data, why?
EIDT
After I add standard setter/getter, everything is ok now.
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
public String getContent()
{
return content;
}
public void setContent(String content)
{
this.content = content;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getWeather()
{
return weather;
}
public void setWeather(String weather)
{
this.weather = weather;
}
Is this cause by jackson mapper ? How can I use fluent API with this ?Why not just use reflection to generate JSON ?
EDIT
What I need is this configuration
#Configuration
#Import(RepositoryRestMvcConfiguration.class)
public class ShoweaRestMvcConfiguration extends RepositoryRestMvcConfiguration
{
#Override
protected void configureJacksonObjectMapper(ObjectMapper mapper)
{
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
}
Caused by this
#Accessors is probably stepping over the #Data annotation, and with fluent = true it generates getters with the same name as the field, like id() and date() (#Accessor documentation). That's why Spring doesn't see any of the fields.
I think you can safely remove both #Accessors and #Access, since #Access's takes the default value from id (if you annotated the field, it will be FIELD, if you annotated the getter, it will be PROPERTY).

Spring MVC 3.1 : how to map JSON from a PUT request body?

I know this question has been asked a gazillion times, but I still cannot find a solution to my problem, which basically boils down to JSON deserialization from a PUT request.
I've already added HiddenHttpMethodFilter as a filter.
org.codehaus.jackson.jackson-mapper-lgpl is in the classpath.
Here is the client part:
$.ajax({
url: '/occurrence',
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify({id:id,startDate:startDate, endDate:endDate, frequencyType:frequency})
})
Here is the controller part:
#Controller
#RequestMapping("/occurrence")
public class OccurrenceController {
private static final String COMMAND = "eventCommand";
#Autowired
private PersistenceCapableOccurrence occurrenceDao;
#Autowired
private PersistenceCapableFrequencyType frequencyTypeDao;
#InitBinder(COMMAND)
public void customizeConversions(final WebDataBinder binder) {
DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm");
df.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(df, true));
EntityConverter<FrequencyType> frequencyTypeEntityConverter = new EntityConverter<FrequencyType>(frequencyTypeDao, FrequencyType.class, "findByValue", String.class);
((GenericConversionService) binder.getConversionService()).addConverter(frequencyTypeEntityConverter);
}
#RequestMapping(method = PUT, consumes = "application/json")
#ResponseBody
public Long saveOccurrence(#RequestBody Occurrence occurrence) {
return occurrenceDao.saveOrUpdate(occurrence);
}
}
Here are my two domain classes (Occurrence and FrequencyType):
public class Occurrence {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", nullable = false)
private long id;
#NotNull
#Column(name = "start_date")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime startDate;
#Column(name="end_date")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime endDate;
#ManyToOne
#JoinColumn(name = "frequency_type", nullable = false)
private FrequencyType frequencyType;
/* C-tor (1 with [start,end,freq], another with [start,freq]), getters (no setters) */
}
#Entity
#Table(name = "frequency_types")
public class FrequencyType {
public enum FrequencyTypeValues {
ONCE, DAILY, WEEKLY, MONTHLY, YEARLY;
}
private String value;
public FrequencyType() {}
public FrequencyType(FrequencyTypeValues value) {
this.value = value.name();
}
#Id
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
//validates value against the enumerated/allowed values (ie throws exceptions if invalid value)
FrequencyTypeValues.valueOf(value.toUpperCase());
this.value = value;
}
}
All I get at the end is a 400 response.
Example :
PUT Request
{"id":"","startDate":"20/10/2012 17:32","endDate":"","frequencyType":"YEARLY"}
Response
"NetworkError: 400 Bad Request - http://localhost:9999/occurrence"
Thanks in advance for your help !
Rolf

Resources