How to hide a request field in Swagger API - java

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 😱)

Related

Spring Data JPA Required request body is missing

I am using the following class as an entity, and a controller class to write data on it:
#Entity
#Table(name = "TableA")
public class TableA {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, name="Id")
private BigInteger id;
#Column(nullable = false, name="Name")
private String name;
//Setters Getters
}
#RestController
public class TableAController {
#Autowired
TableARepository tableARepository;
#Transactional(rollbackFor = Exception.class)
#PostMapping(value="/CreateTableA")
public void createTableA(#RequestBody TableA newTableA){
TableA tableA = new TableA();
tableA = newTableA;
tableARepository.save(tableA);
}
}
The Id column value will be generated by the DB, so I used the #JsonProperty. But, when I test the REST API using the following as a request:
{
"name" : "Leo Messi"
}
I am getting the aforementioned error message. I have also tried the #JsonIgnore property with the same result. Is there a way to except the id property from the deserialization process? Or should I use another class dedicated the API Request? I am not comfortable with creating different models for every new API.
I am not sure if I should focus on resolving the error, or if I should design the classes using a Design Pattern that never produces it.

Jackson Ignores Parent Class properties sent via JSON

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

how to convert from String to enum during BeanUtils.copyProperties in Spring Boot Rest API

I have to copy the properties from dto to entity class.
I am using BeanUtils.copyProperties().
In request body I am sending like below:
{
"userName":"test",
"userStatus": "I",
}
DTO class:
public class UserDto {
private String userName;
private String userStatus;
public User buildUser() {
User user = new User();
BeanUtils.copyProperties(this, user);
return user;
}
}
Entity class:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "user_name")
private String userName;
#Enumerated(EnumType.STRING)
#Column(name = "user_status")
private UserStatus userStatus;
}
note: userStatus can be nullable field in table.
Service code:
User user = userDto.buildUser();
I am getting userStatus value as null in User entity class.
When I changed UserDto.userStatus to enum type, then request body is not accepting empty value.
How do I convert from String to enum during BeanUtils.copyProperties() ?
Spring BeanUtils is not designed for such customizations.
You should set the field manually with.
While MapStruct or Dozen are.
As alternative to keep BeanUtils and no explicit setter invocation you have :
defining a factory method for the enum Jackson processing (a static method annotated #JsonCreator in the enum class such as :
#JsonCreator public static UserStatus getValue(String name) {
return
Stream.of(UserStatus.values())
.findAny(s -> s.name().equals(name))
.orElse(null);
}
In most of cases, this is the best solution as it handles the issue at the root.
setting the flag to ignore unknown value for any field of the class:
public class UserDto {
#JsonIgnoreProperties(ignoreUnknown = true)
//...
}
Fastest solution but I don't like a lot as it may hide some other serialization/deserialization issues.
adding an enum value representing the emptiness. You could so define the enum in the DTO.
In order to not store it in the database, the mapping of this enum value to null should be done in the entity itself.
For example :
public void setUserStatus(UserStatus userStatus){
if (userStatus != UserStatus.EMPTY){
this.userStatus = userStatus;
}
}
It should work but I am not a big fan either...
Enums cannot be null because their underlining values are int but you can set the FIRST value in the enum as a default value. tyou can also define your field in DTO as an Enum type instead of String.
UserStatus
public enum UserStatus {
NULL,
ACTIVE,
INACTIVE;
}
Service code:
userDto.setUserStatus(UserStatus.NULL);
userDto.buildUser();
OR If you want to set this override of copyProperties method to ignore userStatus field while converting:
public static void copyProperties(Object source, Object target,
#Nullable Class<?> editable,
#Nullable String... ignoreProperties);

API Rest with Spring Boot

I'm seeing some videos about API Rest with Spring Boot and so far I've done some basics and when I tried to increase the complexity I'm getting caught.
My idea is in the Post / class, create a new class with students getting the following json:
{
"nome": "Primeira Serie - A".
"alunos": [
"João",
"José",
"Maria"
]
}
And return:
{
"id_classe": 101
}
It happens that it saves the class, but it does not save the students and I have no idea how to show only the id of the class.
I have created the following classes in Java:
Model
Classe.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "classe")
public class Classe {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#OneToMany(mappedBy = "classe")
private Set<Aluno> alunos = new HashSet<Aluno>();
//Get's e Set's suppressed
}
Aluno.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "aluno")
public class Aluno {
private static int tempID = 0;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#ManyToOne
#JoinColumn(name = "id_classe")
#JsonBackReference
private Classe classe;
public Aluno(String nome) {
tempID++;
this.id = tempID;
this.nome = nome;
}
public Aluno() {
}
//Get's e Set's suppressed
}
Repository
ClasseRepository.java
package com.example.classe.repository;
//Import's suppressed
#Repository
public interface ClasseRepository extends JpaRepository<Classe, Integer> {
public List<Classe> findAll();
}
Controller
ClasseController.java
package com.example.classe.controller;
//Import's suppressed
#RestController
#RequestMapping("/classe")
public class ClasseController {
#Autowired
private ClasseRepository classeRepo;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Classe>> getClasse() {
return new ResponseEntity<>(classeRepo.findAll(), HttpStatus.OK);
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> salvarClasse(#RequestBody Classe classe) {
return new ResponseEntity<>(classeRepo.saveAndFlush(classe), HttpStatus.CREATED);
}
}
Am I doing everything wrong or did I not understand the concept? But I wanted to understand how to do it that way.
Thanks in advance.
Cesar Sturion
What you want to achieve is totally doable, but requires several changes.
I split my answer into 2 parts:
Save the students
There are several problems with saving:
On POST your incoming json deserialized into objects in which Classe has a reference to Anuli, but Anuli doesn't have a reference toClasse. To check it you can add a break point at the line: return new ResponseEntity<>(... , run in debug mode and check fields of Anuli in Classe. To fix it you can add #JsonManagedReference on aluni field in Classe. Related question
Hibernate can't save referenced objects by default. You have to save them one by one after saving your Classe object or just turn on Cascade persisting. Related question
So, to fix 1 and 2 Classe should have:
#OneToMany(mappedBy = "classe", cascade = CascadeType.PERSIST)
#JsonManagedReference
private Set<Aluno> alunos = new HashSet<Aluno>();
You have to remove custom id generation in Alumi (I am talking about static int tempID). Annotation #GeneratedValue will perfectly generate id for you as soon as you persist an object. This custom generation breaks Hibernate support. I even not talking about that it also breaks the app after restart, not threadsafe etc.
Return id only
On POST returned json represent what was returned in classeRepo.saveAndFlush(classe) so it's an object of Classe.
If you want to return exactly this:
{
"id_classe": 101
}
Then create new class like this:
public class ClasseIdVO {
#JsonProperty("id_casse")
private Integer id;
// Constructors, getter, setter
VO - means View Object, so this object only for representation, not for persisting, etc.
You can use field name id_casse, but it's against Java code convention, so better add #JsonProperty.
Also change your saving code to new ClasseIdVO(classeRepo.saveAndFlush(classe).getId())
Or you can just return id as a number: classeRepo.saveAndFlush(classe).getId()

JAX-RS (Jersey) creates related object instead of null

problem is rather simple, but kinda hard to explain.
I have REST service like this:
#Path("categories")
#RequestScoped
public class CategoryResource {
#Inject
CategoriesFacade dao;
#PUT
#Consumes("application/json")
public void putJson(Categories content) {
System.err.println(content.toString()); // <-- ?????
dao.edit(content);
}
// ....
}
And entity object like this:
#XmlRootElement
public class Categories implements Serializable {
#Id
#Column(name = "CATEGORY_ID", unique = true, nullable = false, precision = 5)
private Integer categoryId;
#Column(name = "NAME")
private String name;
#JoinColumn(name = "PARENT_ID", referencedColumnName = "CATEGORY_ID")
#ManyToOne
private Categories parentId;
// ....
}
Here is http request payload:
PUT http://localhost:8080/api/categories
Data: {"categoryId":"7162","name":"test","parentId":null}
And here is Categories object i get in #PUT request (see line marked with ????):
Categories{
categoryId=7162,
name=test,
parentId=Categories{categoryId=null, name=null, parentId=null}
}
So as you can see here, parentId is assigned with empty Categories object, this way
i'm getting ValidationException, because name could not be null.
Any idea how to make Jersey to give me this kind of object, so null value for parentId will be fine:
Categories{
categoryId=7162,
name=test,
parentId=null
}
Stringify your object on client site (for example, if client on JavaScropt, using method JSON.stringify).
Rest method make
#PUT
#Consumes("application/json")
public void putJson(String content) {
And convert json string to object (for eample, using google json)
Categories cont = new com.google.gson.Gson().fromJson(content, Categories.class);

Categories

Resources