Different validation JSON responses in Spring Data REST - spring

I'm using Spring Boot, Spring Data REST, Hibernate, JPA.
I'm experiencing a strange issue, probably due to a wrong configuration.
I'm posting relevant parts of my configuration:
#Configuration
public class RestConfig extends RepositoryRestConfigurerAdapter {
#Autowired
private Validator validator;
public static final DateTimeFormatter ISO_FIXED_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
.withZone(ZoneId.of("Z"));
#Bean
public RootResourceProcessor rootResourceProcessor() {
return new RootResourceProcessor();
}
#Override
public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
super.configureValidatingRepositoryEventListener(validatingListener);
}
}
Configuration of validator an message source:
#Configuration
#EnableTransactionManagement
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class CustomConfiguration {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/i18n/messages");
// messageSource.setDefaultEncoding("UTF-8");
// set to true only for debugging
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
/**
* Enable Spring bean validation
* https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
*
* #return
*/
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
my bean:
#Entity
#EntityListeners(MovementListener.class)
public class Movement extends AbstractEntity {
private static final long serialVersionUID = -5073555669170105151L;
#NotBlank
#Column(nullable = false)
private String description;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private PaymentType paymentType;
#NotNull
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private Direction direction;
#Min(value = 0)
#NotNull
#Column(nullable = false)
private BigDecimal amount = BigDecimal.ZERO;
#NotNull
#Column(nullable = false)
private Instant currencyDate = Instant.now();
#JsonProperty(access = Access.READ_ONLY)
#ApiModelProperty(readOnly = true)
#NotNull(message = "{NotNull.movement.agent}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
protected User agent;
#JsonProperty(access = Access.READ_ONLY)
#ApiModelProperty(readOnly = true)
#NotNull(message = "{NotNull.movement.checkpoint}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
protected CheckPoint checkPoint;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
private Customer customer;
#Type(type = "json")
#Column(columnDefinition = "json")
private String details;
and related listener:
#Component
public class MovementListener {
private Logger log = LogManager.getLogger();
public static WorkSessionRepository workSessionRepository;
#Autowired
public void init(WorkSessionRepository workSessionRepository) {
MovementListener.workSessionRepository = workSessionRepository;
}
#PrePersist
private void onSaveOrUpdate(Movement value) {
try {
if (value != null && value.isNew()) {
WorkSession workSession = workSessionRepository.findByAgentUsernameAndEndDateIsNull();
if (workSession != null) {
value.agent = workSession.getAgent();
value.checkPoint = workSession.getCheckPoint();
}
}
} catch (Exception e) {
log.error("Error into MovementListener during #PrePersist.", e);
}
}
}
I'm exposing the persist method of this bean via Spring data REST:
#Transactional
#PreAuthorize("isAuthenticated()")
public interface MovementRepository extends PagingAndSortingRepository<Movement, Long> {
}
If I enable the validator both in beforeCreate and beforeSave, when I POST my entities with errors I see a well formatted ConstraintViolationException JSON error.
Wrong request:
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/hal+json' -d '{ \
"amount": 0, \
"currencyDate": "2017-10-10T20:49:57.959Z", \
"description": "string", \
"details": "string", \
"direction": "IN", \
"paymentType": "", \
"version": 0 \
}' 'http:/
then I've a well formatted reply:
{
"errors": [
{
"entity": "Movement",
"property": "checkPoint",
"invalidValue": null,
"message": "Ogni movimento contabile deve essere associato ad un checkpoint. Ripetere lautenticazione e ripetere loperazione."
},
{
"entity": "Movement",
"property": "paymentType",
"invalidValue": null,
"message": "Il campo non può essere vuoto. Inserire un valore valido e ripetere loperazione."
},
{
"entity": "Movement",
"property": "agent",
"invalidValue": null,
"message": "Ogni movimento contabile deve essere associato ad un operatore. Ripetere lautenticazione e ripetere loperazione."
}
]
}
But, because two fields of the bean are set inside the listener I created, I can't leave enabled the beforeCreate event and I should just leave only beforeSave.
If I do this, with the same request, I've this reply:
{
"timestamp": "2017-10-10T21:04:39.450+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ConstraintViolationException",
"message": "Validation failed for classes [it.rebus.server.model.accounting.Movement] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=paymentType, rootBeanClass=class it.rebus.server.model.accounting.Movement, messageTemplate='{javax.validation.constraints.NotNull.message}'}\n]",
"path": "/api/v1/movements"
}
Seems Spring Data REST is not managing correclty the ConstraintViolationException but I don't understand why. I would need a hint to pick up the right way to solve the problem.

Related

Why am i getting nulls in parameters inside json?

this is small spring-boot project
I am unable to get users parameteres
I still can create other users in db using POST method, but when i am trying get list of all users or only one by id, i am getting json with nulls in users parameters.
I want to get this from getUsers method
{
"id": 1,
"name": testName,
"password": testPassword,
"email": testEmail,
"phoneNumber": testPhoneNumber,
"status": USER
},
Instread of this i am getting
{
"id": null,
"name": null,
"password": null,
"email": null,
"phoneNumber": null,
"status": null
},
Here is my entity class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",nullable = false)
private Integer id;
#Column(name = "name",nullable = false)
private String name;
#Column(name = "password",nullable = false)
private String password;
#Column(name = "email",nullable = false)
private String email;
#Column(name = "phoneNumber",nullable = false)
private String phoneNumber;
#Column(name = "status",nullable = false)
private Status status;
}
My controller
#Controller
#RequestMapping
#RequiredArgsConstructor
#Validated
public class UserController {
private final UserService userService;
#Operation(description = "Getting list of users")
#GetMapping(
value = "/v1/users",
produces = {"application/json"}
)
public ResponseEntity<List<UserDto>> getUsers() {
return ResponseEntity.ok(userService.getUsers());
}
My service
#Service
#RequiredArgsConstructor
#Data
public class UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
public List<UserDto> getUsers() {
return StreamSupport.stream(userRepository.findAll().spliterator(), false)
.map(userMapper::userToUserDto).collect(Collectors.toList());
}

Error locating String field in Spring Boot

I'm trying to find a company by its CNPJ(Brazilian corporate tax payer registry number) in a DB (H2), but it's returning an error
{
"timestamp": "2022-03-30T19:30:23.823+00:00",
"status": 404,
"error": "Not Found",
"path": "/companies/cnpj/30101554000146"
}
I've tried other alternatives using:
http://localhost:8080/companies/cnpj/'30.101.554/0001-46', http://localhost:8080/companies/cnpj/"30.101.554/0001-46",
but the error persists. I implemented like this :
#Entity
#Table(name = "company")
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#CNPJ
private String cnpj;
//skipped
}
public interface CompanyRepository extends JpaRepository<Company,Long> {
Optional<Company> findByCnpj(String cnpj);
}
public class CompanyDTO {
private Long id;
private String name;
private String cnpj;
//skipped
}
#Service
#Transactionalpublic class CompanyService {
#Autowired
private CompanyRepository companyRepository;
#Transactional(readOnly = true)
public CompanyDTO findById(Long id) {
Company resultado = companyRepository.findById(id).get();
CompanyDTO dto = new CompanyDTO(resultado);
return dto;
}
#Transactional(readOnly = true)
public CompanyDTO findByCnpj(String cnpf) {
Optional<Company> resultado = companyRepository.findByCnpj(cnpf);
CompanyDTO dto = new CompanyDTO(resultado.get());
return dto;
}
}
#RestController
#RequestMapping(value = "/companies")public class CompanyController {
#Autowired
private CompanyService companyService;
#GetMapping(value = "/{id}")
public CompanyDTO findById(#PathVariable Long id) {
return companyService.findById(id);
}
#GetMapping(value = "/cnpj/{cnpj}")
public CompanyDTO findByCnpj(#PathVariable String cnpj) {
return companyService.findByCnpj(cnpj);
}
}
The expected output would be:
[
{"id": 1,
"nome": "Company 123",
"cnpj": "30.101.554/0001-46"
}
]
UPDATE:
I changed #GetMapping(value = "/cnpj/{cnpj}") to #GetMapping(value = "/cnpj/**") and:
#GetMapping(value = "/cnpj/**")
public CompanyDTO findByCnpj(HttpServletRequest request) {
return companyService.findByCnpj(request.getRequestURI().split(request.getContextPath() + "/cnpj/")[1]);
}
Works for me! Thanks
As explained here, pathParams with slashes can be realy tricky while using spring-boot. This article explains pretty well what to do to avoid getting an error 404 when your pathVariable has a slash.

Handling Authentication Failure with Springboot & Spring security

In a Rest appplication developped with Spring, I use POJO classes, DTO and entity for users management. Here is an abstract of my entity class.
#Entity
#Table(name="users")
#Getter #Setter
#AllArgsConstructor #NoArgsConstructor
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String userKeyId;
#Column(nullable = false, length = 50)
private String firstName;
#Column(nullable = false, length = 50)
private String lastName;
#Column(nullable = false, length = 120, unique = true)
private String email;
#Column(nullable = false)
private String encryptedPassword;
#Column
private String emailVerificationToken;
#Column(name = "email_verification_status", columnDefinition = "BOOLEAN NOT NULL DEFAULT FALSE")
private Boolean emailVerificationStatus = false;
#Column(name="is_account_non_expired")
private Boolean isAccountNonExpired;
#Column(name="is_account_non_locked")
private Boolean isAccountNonLocked;
#Column(name="is_credentials_non_expired")
private Boolean isCredentialsNonExpired;
#Column(name="is_enabled")
private Boolean isEnabled;
#Column(name="is_logged_in")
private Boolean isLoggedIn;
#ManyToMany(cascade= { CascadeType.PERSIST }, fetch = FetchType.EAGER )
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name = "role_id", referencedColumnName = "id"))
private Collection<RoleEntity> roles;
#CreationTimestamp
#Temporal(TemporalType.DATE)
#Column(name="created_at")
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.DATE)
#Column(name="updated_at")
private Date updatedAt;
}
I have a UserServiceImpl class that implements UserDetails
I do have then to implement loadUserByUsername
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByEmail(email);
if( userEntity == null) {
throw new UsernameNotFoundException("User email is not in the database");
} else {
validateLoginAttempt(userEntity);
log.info("Returning User : " + userEntity.getFirstName() + " " + userEntity.getLastName());
userEntity.setLastLoginDateDisplay(userEntity.getLastLoginDate());
userEntity.setLastLoginDate(new Date());
userRepository.save(userEntity);
return new UserPrincipal(userEntity);
}
}
If user exists I call a method to validate authentication.
private void validateLoginAttempt(UserEntity user) {
if(user.getIsAccountNonLocked()) {
if(loginAttemptService.hasExceededMaxAttempts(user.getEmail())) {
user.setIsAccountNonLocked(Boolean.FALSE);
} else {
user.setIsAccountNonLocked(Boolean.TRUE);
}
} else {
loginAttemptService.evictUserFromLoginAttemptCache(user.getEmail());
}
}
This method allows me to check if the user account is locked or not and if user tried to connect too many times.
My LoginAttemptServiceImpl is the following:
#Service
public class LoginAttemptServiceImpl implements LoginAttemptService {
public static final int MAXIMUM_AUTH_ATTEMPT = 5;
public static final int AUTH_ATTEMPT_INCREMENT = 1;
private LoadingCache<String, Integer> loginAttemptCache;
private String username;
public LoginAttemptServiceImpl() {
super();
loginAttemptCache = CacheBuilder.newBuilder()
.expireAfterWrite(15, TimeUnit.MINUTES)
.maximumSize(10000)
.build(new CacheLoader<>() {
#Override
public Integer load(String key) {
return 0;
}
});
}
#Override
public void evictUserFromLoginAttemptCache(String username) {
loginAttemptCache.invalidate(username);
}
#Override
public void addUserToLoginAttemptCache(String username) {
int attempts = 0;
try {
attempts = AUTH_ATTEMPT_INCREMENT + loginAttemptCache.get(username);
loginAttemptCache.put(username, attempts);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
#Override
public boolean hasExceededMaxAttempts(String username) {
try {
return loginAttemptCache.get(username) >= MAXIMUM_AUTH_ATTEMPT;
} catch (ExecutionException e) {
e.printStackTrace();
}
return false;
}
#Override
public int getLoginAttempts(String username) throws ExecutionException {
return loginAttemptCache.get(username);
}
}
I also implemented an event listener for authentication failure:
#Component
public class AuthenticationFailureListener {
private final LoginAttemptService loginAttemptService;
#Autowired
public AuthenticationFailureListener(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService;
}
#EventListener
public void onAuthenticationFailure(AuthenticationFailureBadCredentialsEvent event) {
Object principal = event.getAuthentication().getPrincipal();
if (principal instanceof String) {
String username = (String) event.getAuthentication().getPrincipal();
loginAttemptService.addUserToLoginAttemptCache(username);
}
}
}
And finally my AuthenticationFilter allows me to manage successful and unsuccessful response:
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String userName = ((UserPrincipal)authResult.getPrincipal()).getUsername();
// built the token
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.getTokenSecret())
.compact();
UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImpl");
UserDto userDto = userService.getUser(userName);
response.addHeader(SecurityConstants.HEADER_STRING_USERID, userDto.getUserKeyId());
response.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
}
#SneakyThrows
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
// super.unsuccessfulAuthentication(request, response, failed);
int attempts;
if(loginAttemptService.hasExceededMaxAttempts(this.username)) {
attempts = loginAttemptService.getLoginAttempts(this.username);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Attempt number " + attempts + ": Account is locked for 15 minutes");
} else {
attempts = loginAttemptService.getLoginAttempts(this.username);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Attempt number " + attempts + ": " + (SecurityConstants.MAX_AUTH_ATTEMPTS - attempts) + " - before account is blocked");
}
}
Authentication works when it's successful... My issue concerns failure and i have 3 issues:
I would like to return an object in case of failure. the response.sendError should do the job but it doesn't. I also tried to return a Json response : https://www.baeldung.com/servlet-json-response
I use Guava cache but I also update database at the same time by setting isAccountNonLocked to false. I'd like to set the value to True once the cache is cleared.
I do not update the count of attempt in unsuccessfulAuthentication method. My response is always : Attempt number 0: 5 - before account is blocked
Thanks for help and for reading the whole text!
Regarding issue number 1, you can use a similar approach as the one mentioned in the link you posted, but use response.getWriter().write(String) and Jackson's ObjectMapper, like this:
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ObjectMapper mapper = new ObjectMapper();
response.getWriter().write(mapper.writeValueAsString( /*Your custom POJO here */ ));
For issue 2: I found a trick that solves it. Instead of updating the database at the same time i clear the cache, I make the update at login validation...
private void validateLoginAttempt(UserEntity user) {
if(user.getIsAccountNonLocked()) {
if(loginAttemptService.hasExceededMaxAttempts(user.getEmail())) {
user.setIsAccountNonLocked(Boolean.FALSE);
} else {
user.setIsAccountNonLocked(Boolean.TRUE);
}
} else {
if(!loginAttemptService.hasExceededMaxAttempts(user.getEmail())) {
user.setIsAccountNonLocked(Boolean.TRUE);
}
loginAttemptService.evictUserFromLoginAttemptCache(user.getEmail());
}
}
For issue 3:
In my WebSecurity class which extends WebSecurityConfigurerAdapter, I implemented a bean in order to inject it in my AuthenticationFilter.
Here is my bean:
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Here is my AuthenticationFilter class. I initially added this class as component (bad idea which generated error messages).
// #Component
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final LoginAttemptService loginAttemptService;
private String username;
public AuthenticationFilter(AuthenticationManager authenticationManager, LoginAttemptService loginAttemptService) {
this.authenticationManager = authenticationManager;
this.loginAttemptService = loginAttemptService;
}
....

SnippetException: Cannot document response fields as the response body is empty

Here is my Controller:
#PostMapping("post")
#PreAuthorize("hasAuthority('WRITE')")
public ResponseEntity<?> createPost(#RequestBody PostEntity postEntity) {
return new ResponseEntity<>(postService.createPost(postEntity), HttpStatus.CREATED);
}
Service :
#Override
public Post createPost(PostEntity postEntity) {
return postFactory.buildPost(postEntityRepository.save(postEntity));
}
//Post is Immutable class here
public Post buildPost(PostEntity entity) {
return new Post.Builder()
.groupId(entity.getGroupEntity().getGroupId())
.postedBy(entity.getPostedBy().getUsername())
.postType(entity.getType())
.postMedia(entity.getPostMedia())
.postId(entity.getPostId())
.build();
}
Here is my mockMvc:
#BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}",
preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
Here is my Test:
this.mockMvc.perform(post("/api/post")
.contentType(MediaTypes.HAL_JSON)
.contextPath("/api")
.content(this.objectMapper.writeValueAsString(postEntity)))
.andExpect(status().isCreated())
.andDo(
document("{method-name}", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
requestFields(describeCreatePostRequest()),
responseFields(describePostEntityResult())
));
Here is Post call:
#Value.Immutable
#JsonSerialize(as = ImmutablePost.class)
#JsonDeserialize(as = ImmutablePost.class)
#JsonInclude(JsonInclude.Include.NON_NULL)
#Relation(value = "post", collectionRelation = "posts")
public interface Post {
Long getPostId();
String getPostType();
String postedBy();
#Nullable
PostMedia postMedia();
Long groupId();
class Builder extends ImmutablePost.Builder {}
}
PostEntity #Entity class here is json format:
{
"type" : "text",
"postedBy" : {
"username": "sandeep"
},
"postMedia" : {
"mediaType": "text",
"mediaUrl": "null",
"content": "Hi this is testing media content",
"createdAt": 1234,
"updatedAt": 689
},
"groupEntity": {
"groupId": 4
}
}
And Post entity class:
#Entity
#Table(name = "POST")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class PostEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "POST_ID")
private Long postId;
#Column(name = "POST_TYPE")
private String type;
#ManyToOne
#JoinColumn(name = "username", referencedColumnName = "username")
private User postedBy;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "post_media_id", referencedColumnName = "id")
private PostMedia postMedia;
#ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
#JoinColumn(name = "GROUP_ID", referencedColumnName = "GROUP_ID")
private GroupEntity groupEntity;
public PostEntity() {
}
}
I have tried
objectMapper.readValue(objectMapper.writeValueAsString(postEntity), ImmutablePost.class);
As well. but still its not working, I am facing same exception:
org.springframework.restdocs.snippet.SnippetException: Cannot document response fields as the response body is empty
at org.springframework.restdocs.payload.AbstractFieldsSnippet.verifyContent(AbstractFieldsSnippet.java:191)
at org.springframework.restdocs.payload.AbstractFieldsSnippet.createModel(AbstractFieldsSnippet.java:147)
at org.springframework.restdocs.snippet.TemplatedSnippet.document(TemplatedSnippet.java:78)
at org.springframework.restdocs.generate.RestDocumentationGenerator.handle(RestDocumentationGenerator.java:191)
The problem is with the "PostMedia" setters "PostMedia" and "GroupEntity". You need to accept strings in the setters parameter and convert them to the corresponding entity. Expecting the setter's arguments as the custom entity type is making the problem.
For example:
public class MyModel {
private CustomEnum myEnum;
public CustomEnum getMyEnum() {
return myEnum;
}
public void setMyEnum(String enumName) {
this.myEnum = CustomEnum.valueOf(enumName);
}
}

restTemplate.getForObject doesn't read json array information

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.

Resources