Jackson Ignores Parent Class properties sent via JSON - java

I am creating REST Service with spring boot and defined some classes with inheritence, however I'm not able to receive a JSON payload which I am sending from postman to the controller.
JSON Payload which I'm sending :
{
"dummy" : "okok",
"fullName": "okok",
"mobileNumber": 1234567890
}
I am only getting dummy property in the controller, rest of the properties not getting mapped to POJO.
Logging statement prints following line on the console
ownerAccount OwnerAccount(dummy=okok)
I think only OwnerAccount constructor is getting invoked and Account properties not getting initialized.
Please help me understand the missing part or mistake I am doing here.
I have defined following structure :
Account.java
#NoArgsConstructor
#AllArgsConstructor
#Data
public class Account {
#NotBlank(message = "fullName is mandatory")
private String fullName;
#NotNull(message = "mobileNumber is mandatory")
private Long mobileNumber;
#Valid
private AddressRequest addressRequest;
}
OwnerAccount.java
#NoArgsConstructor
#Data
public class OwnerAccount extends Account {
#NotBlank(message = "dummy is mandatory")
private String dummy;
}
OwnerController.java
#RestController
#RequestMapping(path = "/api/v1/account/owner")
public class OwnerAccountResource {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private OwnerAccountService ownerAccountService;
#Autowired
public OwnerAccountResource(OwnerAccountService ownerAccountService) {
this.ownerAccountService = ownerAccountService;
}
#PostMapping("/signup")
public ResponseEntity createOwnerAccount(#RequestBody #Valid OwnerAccount ownerAccountRequest) {
logger.info("ownerAccountDto {}", ownerAccountRequest);
return ResponseEntity.ok(ownerAccountService.createAccount(ownerAccountRequest));
}
}

I suppose you think you are not able to receive dummy field because here you are printing only Account fields because #Data add #ToString annotation but it's not printing super class fields
logger.info("ownerAccountDto {}", ownerAccountRequest);
but if you could debug that controller you would see dummy field is there. You need to override toString() to log dummy field or just add lombok annotation
#NoArgsConstructor
#Data
#ToString(callSuper = true)
public class OwnerAccount extends Account {
#NotBlank(message = "dummy is mandatory")
private String dummy;
}
#ToString(callSuper = true) will include fields from super class

Related

How to inject List of non-primitive Objects in Spring #Value

Hi Spring/SpringBoot/Yaml/Java Experts, I want to inject a list of values that belong to the class Perm in #Value
However, Spring fails to start my application and is throwing the error shown below:
Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'myservice.api.perms' in value "${myservice.api.perms}"
May I request you to help me as I am new to Spring, Spring Boot, and Yaml
# application.yaml file
myservice:
api:
perms:
-
name: xyz
guid: abc
index: 0
// Class file that wants to read a List<Perm> from the application.yaml file
#Slf4j
#Getter
#Service
#Accessors(fluent = true)
public class MyService {
#Value("${myservice.api.perms}")
private List<Perm> perms;
public List<Perm> getListOfPerms() {
return perms;
}
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
public static class Perm {
#NotBlank
private String name;
#NotBlank
private String guid;
private int index;
}
}
You should use #ConfigurationProperties in this case!
#Configuration
#ConfigurationProperties("myservice.api")
#Getter
#Setter
#Validated
public class ApiProperties {
#NestedConfigurationProperty
#NotEmpty
private List<#Valid Perm> perms;
#Getter
#Setter
public static class Perm {
#NotBlank
private String name;
#NotBlank
private String guid;
#NotNull
#Positive
private Integer index;
}
}
More resources:
Guide to #ConfigurationProperties in Spring Boot (Baeldung)
Type-safe Configuration Properties (Spring reference)

How to Use #JsonProperty With Lombok?

Suppose we have a json response that we want to map it to our java class.
{
"access_token": "abcdefg..."
}
I had a data class that mapped access_token field in the json to accessToken field in the code. I used to use #JsonProperty annotation on getters and setters.
private String accessToken;
#JsonProperty("accessToken")
public String getAccessToken() {
return accessToken;
}
#JsonProperty("access_token")
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
Then I decided to use Lombok annotations #Getter and #Setter. Since I do not have a getter and setter in my code, how can I map access_token field in the json to accessToken field in the code with Lombok annotations?
My code is like this right now and as you can expect, it cannot map the fields.
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Token {
private String accessToken;
}
I do not want to name my variable to access_token because I also return access token as a json response and I want it to appear as accessToken in my json response.
Thank you.
Found a way to do it.
#NoArgsConstructor
#AllArgsConstructor
public class Token {
#Setter(onMethod = #__(#JsonSetter(value = "access_token")))
#Getter(onMethod = #__(#JsonGetter(value = "accessToken")))
private String accessToken;
}
Another solution is to use #JsonAlias annotation.
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Token {
#JsonAlias("access_token")))
private String accessToken;
}
No need for all this, you can simply annotate your field with #JsonProperty. Spring will still use the getters for serialization.
#Data
public class Token {
#JsonProperty("access_token")
private String accessToken;
}

Jackson #JsonIgnoreProperties isn't working on referenced class property?

I am trying to make a model (Request) class that would ignore a nested class's property it references. And I was told that I cannot modify referenced model (User) in any way, so what I had tried was to ignore its property from the Request model.
#Data
#Builder
public class Request {
#JsonIgnoreProperties(value = {"id"})
User user;
}
// class I cannot modify
#Data
#Builder
public class User {
String id;
String name;
...
}
In the payload, I am still seeing id serialized & deserialized so the annotation clearly doesn't work. It seems to work fine if I place it at the class level of User but since I cannot modify User, I've also tried:
#JsonIgnoreProperties(value = {"user.id"})
public class Request {
User user;
None of above works. The doc is saying
Starting with 2.0, this annotation can be applied both to classes and to properties
I am using Jackson 2.10.2 in a Spring Boot project. What am I missing?
Create a class who extends User as bellow:
public class MyUser extends User {
#JsonIgnore
String id;
}
#Data
#Builder
public class Request {
MyUser user;
}
I'm running this test and works:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class Request {
#JsonIgnoreProperties("id")
User user;
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class User {
String id;
String name;
}
public class TestClass {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper= new ObjectMapper();
final Request value = Request.builder()
.user(User.builder()
.id("qw423432")
.name("asdasdjsadjasdasd")
.build())
.build();
System.out.println(mapper.writeValueAsString(value));
System.out.println(mapper.readValue("{\"user\":{\"id\": \"a\", \"name\":\"b\"}}", Request.class));
}
}
Do you see any difference with your test?

#BasePathAwareController Exception: could not initialize proxy - no Session

#BasePathAwareController
public class MetricController {
#Autowired
private MetricRepository metricRepository;
#Transactional
#RequestMapping(method = RequestMethod.GET, value = "/metrics/in/{id}")
public #ResponseBody
MetricDTO getMetric(#PathVariable Long id) {
return MetricDTO.fromEntity(metricRepository.getOne(id));
}
}
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(
uniqueConstraints = #UniqueConstraint(columnNames = {"metricType", "instanceType"}, name = "customUniqueId")
)
public class Metric implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
private SourceType sourceType;
private String metricTypeField;
private String metricType;
private String instanceType;
private String instanceTypeField;
#ElementCollection
private List<String> metricIdFields;
#ElementCollection
private List<String> valueFields;
#ElementCollection
private Map<String, String> virtualFieldValueEx;
}
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class MetricDTO {
private SourceType sourceType;
private String metricTypeField;
private String metricType;
private String instanceType;
private String instanceTypeField;
private List<String> metricIdFields;
private List<String> valueFields;
private Map<String, String> virtualFieldValueEx;
public static MetricDTO fromEntity(Metric metric) {
return new MetricDTO(
metric.getSourceType(),
metric.getMetricTypeField(),
metric.getMetricType(),
metric.getInstanceType(),
metric.getInstanceTypeField(),
metric.getMetricIdFields(),
metric.getValueFields(),
metric.getVirtualFieldValueEx()
);
}
}
Since #RepositoryRestController in Spring Data Rest is not compatible with Swagger, I changed it to #BasePathAwareController.
So, the problem is that the controller is not working properly.
The error history is as follows.
Could not write JSON: failed to lazily initialize a collection of role: com.jmsight.management.entity.Metric.metricIdFields, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.jmsight.management.entity.Metric.metricIdFields, could not initialize proxy - no Session (through reference chain: com.jmsight.management.dto.MetricDTO["metricIdFields"])
Using #RepositoryRestController works normally.
What is the problem? Is it resolvable?
If you check the source of #BasePathAwareController then you can see that it's not annotated with #Controller.
So if a class is annotated only with #BasePathAwareController then it won't be picked neither by the default SpringMvc neither RepositoryRestMvc.
The former picks classes annotated with #Controller or #RequestMapping, the latter picks only classes annotated with #RepositoryRestController.
So once again, just to make it clear: #BasePathAwareController is NOT an 'extension' of #Controller, it's just a additional 'sign' annotation.
You can use #Controller together with #BasePathAwareController instead of #RequestMapping too.
I believe it's a misleading naming, or simply a bug in the implementation.
One more thing.
If you switch the from #RepositoryRestController to #Controller/#RequestMapping, then your controller will be handled a completely different way.
It could look that it works the same way, but it's called by a completely different handlerMapping: which uses different converters, argumentResolvers, even a different objectMapper.
There could be unpleasant surprises if you need to implement more complicated handler-methods in your controller-class.
I solved it.
To share, #BasePathAwareController should be written in class as #RequestMapping.
I don't know why. If you know reason teach me please.
#BasePathAwareController
#RequestMapping(value = "your url value")
public class MetricController {}

How to hide a request field in Swagger API

I want to hide the "id" item in the model, how to do this in java?
to hide a request field in Swagger API v2:
#ApiModelProperty(hidden = true)
private String id;
in OpenAPI v3:
#Schema(accessMode = Schema.AccessMode.READ_ONLY)
private String id;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#ApiModelProperty(accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String id;
Also see: https://github.com/springfox/springfox/issues/2816
You can use #Hidden with Swagger Core 2.X
#Hidden -- Hides a resource, an operation or a property
Example from above link : Marks a given resource, class or bean type as hidden, skipping while reading / resolving.
#Path("/user")
#Produces({"application/json", "application/xml"})
public class HiddenAnnotatedUserResourceMethodAndData {
UserData userData = new UserData();
#POST
#Hidden
#Operation(summary = "Create user",
description = "This can only be done by the logged in user.")
#Path("/1")
public Response createUser(
#Parameter(description = "Created user object", required = true) User user) {
userData.addUser(user);
return Response.ok().entity("").build();
}
#POST
#Operation(summary = "Create user",
description = "This can only be done by the logged in user.")
#Path("/2")
public Response createUserWithHiddenBeanProperty(
#Parameter(description = "Created user object", required = true) UserResourceBean user) {
return Response.ok().entity("").build();
}
}
Output of above
openapi: 3.0.1
paths:
/user/2:
post:
summary: Create user
description: This can only be done by the logged in user.
operationId: createUserWithHiddenBeanProperty
requestBody:
description: Created user object
content:
'*/*':
schema:
$ref: '#/components/schemas/UserResourceBean'
required: true
responses:
default:
description: default response
components:
schemas:
UserResourceBean:
type: object
properties:
foo:
type: string
Generally using DTO can be useful in this situation, for each entity, define one DTO(it depends on your project but most of the time can be useful) and then map your entity to DTO and vice versa with Mapstruct,after that in your DTO class by using #JsonIgnorProperties annotation on each field you want, you can omit that field from exposing by API services.
✍🏻 Extends yours Entity & Use #JsonIgnoreProperties
public class UserVo {
#JsonIgnoreProperties("password")
public static class Public extends User {}
#JsonIgnoreProperties("userId")
public static class Save extends User {}
#JsonIgnoreProperties("password")
public static class Update extends User {}
}
✍🏻 Entity Class ::
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer userId;
// 🚨 Write only access to password default in entity
// in order to hide it for update case we will extends class behaviour
// in our custom views
// Instead of JsonViews i use custom views (i.e UserVo) with JsonProperty
// #JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#NotBlank
#Schema(example = "lorem1234")
#Column(name = "password")
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
.....
}
✍🏻 Controller Class ::
// See UserVo.Save in request body it serialize this class
#PostMapping(value = "/create")
public ResponseModel<User> createUser(#Valid #RequestBody UserVo.Save user) {
// ... My dirty code comes here 🤡
}
✍🏻 Result ::
Easy Serialization;
No separate Domain classes
Easy Swagger Schema Hide ( Swagger knows hide Id on Add Operation 😱)

Categories

Resources