Least boilerplaty way of creating JSON in Java/Spring Boot? - java

For example in rust, we can use type-safe json! macro like this:-
let value = json!({
"user": json!({
"data" : json!({"
filled": false
})
})
});
What is the similar way in java without creating POJO or string literal?

In spring, you normally don't need lines like your RUST-example. Spring is doing a lot behind the scenes for you.
Two common use-cases
1. Provide a REST-API
Normally, when you use spring, then you want to build a REST-API. A simple API looks like this:
// User.java
#Data // <- Lombok annotation, which creates getter/setter/equals/hashcode for us
public class User {
private String name;
}
// UserController.java
#Controller
#RequiredArgsConstructor
#RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
#GetMapping("/{id}")
public User getUser(#PathVariable("id") String id) {
// spring will serialize the user automatically for you
// there is no need to build a json yourself
return userRepository.getById(id);
}
2. Consume a REST-API
On the other hand, we want to consume a REST-API. But also here: Spring and his ecosystem is doing the JSON-part for you.
// User.java
#Data
public class User {
private String name;
}
// some random method in a class with a webClient
private User consumeRest() {
// even here: no manual json parsing at all
// Spring-magic is working for you
return this.webClient.get()
.uri(someUriHere)
.retrive()
.bodyToMono(User.class)
.block();
}
tl;dr
Spring (and java) requires a strong typing. You need almost always a type-class (aka POJO). The classes can be really complex. Hundred of fields and nested fields? No problem.
There are some "hacks" to bypass the type. Like the link in the comments or a Map<String, Object> but you will loose the type-safety.
If that is too much "boilerplate", than Java is not perhaps the right choice for you.

Related

How to customize the API metadata of springdoc-openai?

I'm trying to customize the springdoc-openapi, make it can work with my framework, but I meet two problems.
1. How to treat methods that do not start with is/get as properties of Model?
If users use my ORM framework by Java language, the property getters in the entity interface can either start with is/get like a traditional Java Bean, or don't start with is/get like a Java record, for example
#Entity
public interface Book {
#Id
long id();
String name();
int edition();
BigDecimal price();
#ManyToOne
BookStore store();
#ManyToMany
List<Author> authors();
}
Here, the wording that does not start with is/get is used, which looks like a java record, not a traditional java bean.
However, doing this will cause swagger-ui to think that the model doesn't have any attributes. So I have to change the behavior of swagger.
After some research, I found that this behavior can be changed using io.swagger.v3.core.converter.ModelConverter, which is the most likely solution.
However, springdoc-openapi does not explain in detail how to use ModelConverter in the documentation. Ultimately, this goal was not achieved.
2. How to control the shape of dynamic objects in HTTP response?
My ORM is GraphQL-style, its entity objects are dynamic so that data structures of arbitrary shapes can be queried, just like GraphQL does. For example
#RestController
public class BookController {
#AutoWired
private JSqlClient sqlClient;
// Query simple book objects
#GetMapping("/books")
public List<Book> books() {
return sqlClient.getEntities().findAll(Book.class);
}
// Query complex book objects
#GetMapping("/books/details")
public List<Book> bookDetails() {
return sqlClient.getEntities().findAll(
// Like the request body of GraphQL
BookFetcher$
.allScalarFields()
.store(
BookStoreFetcher.$.allScalarFields()
)
.authors(
AuthorFetcher.$.allScalars()
)
);
}
}
The first query returns a list of simple book objects in the format {id, name, edition, price}
The second query returns a list of complex book objects in the format {id, name, edition, price, store: {id, name, website}, authors: {id, firstName, lastName, gender}}
Dynamic objects can vary in shape, and these are just two special cases.
I expect swgger to tell the client the shape of the object returned by each business scenario. So, I defined an annotation called #FetchBy. It should be used like this
#RestController
public class BookController {
private static final Fetcher<Book> BOOK_DETAIL_FETCHER =
BookFetcher$
.allScalarFields()
.store(
BookStoreFetcher.$.allScalarFields()
)
.authors(
AuthorFetcher.$.allScalars()
);
#AutoWired
private JSqlClient sqlClient;
#GetMapping("/books")
public List<Book> books() {
return sqlClient.getEntities().findAll(Book.class);
}
#GetMapping("/books/details")
public List<#FetchBy("BOOK_DETAIL_FETCHER") Book> bookDetails() {
return sqlClient.getEntities().findAll(BOOK_DETAIL_FETCHER);
}
}
Declare the shape of the complex object as a static constant.
The #FetchBy annotation uses the constant name to tell swgger the shape of the returned dynamic object.
After some research, I found that this behavior can be changed using org.springdoc.core.customizers.OperationCustomizer, which is the most likely solution.
However, I found that the schema tree of swagger is not consistent with the generic type definition tree in the java language. For example, Spring's ResponseEntity<> wrapper will be ignored by swagger and will be not parsed as a node of schema tree. Theoretically speaking, this ability of swagger can be customized infinitely, so the two trees may not always be consistent and difficult to analyze.

Spring Pageable Sort change name of property

Is there a way to map properties of Sort objects? Just a little example:
a Dto
public class OrderDto {
private long totalAmount;
private long openAmount;
}
and an entity
#Entity
public class Order {
private long amount;
}
and a controller
#RestController
public class OrderController {
#GetMapping("/orders")
public Page<OrderDto> findOrders(Pageable pageable) {
// Do something
}
}
I would now call /orders?sort=totalAmount,desc
Is it possible to map the sort property, in this case the totalAmount from the Dto to only amount to use this for a generated JPA query in a charmant way?
I don't know exactly if I get your question right, but...
Do I understand you correct, that you want to map the request-parameter sort with the value totalAmount,desc to be available through the Sort object of the given Pageable object as amount (with order desc)?
Because of the names you mention in your post, I expect that you have Spring Data on the classpath.
IDKFS but maybe class SortHandlerMethodArgumentResolverCustomizer could help you to customize the SortHandlerMethodArgumentResolver the way, that you can map the request-parameter like you want to have.
If not, it should be possible to provide a #Bean of type SortHandlerMethodArgumentResolver that implements the behavior you want to have.
I'm not completely sure if the framework allows to override the specific part you need to be customized but it is IMO worth to have a look. :)

DTO conveter pattern in Spring Boot

The main question is how to convert DTOs to entities and entities to Dtos without breaking SOLID principles.
For example we have such json:
{ id: 1,
name: "user",
role: "manager"
}
DTO is:
public class UserDto {
private Long id;
private String name;
private String roleName;
}
And entities are:
public class UserEntity {
private Long id;
private String name;
private Role role
}
public class RoleEntity {
private Long id;
private String roleName;
}
And there is usefull Java 8 DTO conveter pattern.
But in their example there is no OneToMany relations. In order to create UserEntity I need get Role by roleName using dao layer (service layer). Can I inject UserRepository (or UserService) into conveter. Because it seems that converter component will break SRP, it must convert only, must not know about services or repositories.
Converter example:
#Component
public class UserConverter implements Converter<UserEntity, UserDto> {
#Autowired
private RoleRepository roleRepository;
#Override
public UserEntity createFrom(final UserDto dto) {
UserEntity userEntity = new UserEntity();
Role role = roleRepository.findByRoleName(dto.getRoleName());
userEntity.setName(dto.getName());
userEntity.setRole(role);
return userEntity;
}
....
Is it good to use repository in the conveter class? Or should I create another service/component that will be responsible for creating entities from DTOs (like UserFactory)?
Try to decouple the conversion from the other layers as much as possible:
public class UserConverter implements Converter<UserEntity, UserDto> {
private final Function<String, RoleEntity> roleResolver;
#Override
public UserEntity createFrom(final UserDto dto) {
UserEntity userEntity = new UserEntity();
Role role = roleResolver.apply(dto.getRoleName());
userEntity.setName(dto.getName());
userEntity.setRole(role);
return userEntity;
}
}
#Configuration
class MyConverterConfiguration {
#Bean
public Converter<UserEntity, UserDto> userEntityConverter(
#Autowired RoleRepository roleRepository
) {
return new UserConverter(roleRepository::findByRoleName)
}
}
You could even define a custom Converter<RoleEntity, String> but that may stretch the whole abstraction a bit too far.
As some other pointed out this kind of abstraction hides a part of the application that may perform very poorly when used for collections (as DB queries could normally be batched. I would advice you to define a Converter<List<UserEntity>, List<UserDto>> which may seem a little cumbersome when converting a single object but you are now able to batch your database requests instead of querying one by one - the user cannot use said converter wrong (assuming no ill intention).
Take a look at MapStruct or ModelMapper if you would like to have some more comfort when defining your converters. And last but not least give datus a shot (disclaimer: I am the author), it lets you define your mapping in a fluent way without any implicit functionality:
#Configuration
class MyConverterConfiguration {
#Bean
public Mapper<UserDto, UserEntity> userDtoCnoverter(#Autowired RoleRepository roleRepository) {
Mapper<UserDto, UserEntity> mapper = Datus.forTypes(UserDto.class, UserEntity.class)
.mutable(UserEntity::new)
.from(UserDto::getName).into(UserEntity::setName)
.from(UserDto::getRole).map(roleRepository::findByRoleName).into(UserEntity::setRole)
.build();
return mapper;
}
}
(This example would still suffer from the db bottleneck when converting a Collection<UserDto>
I would argue this would be the most SOLID approach, but the given context / scenario is suffering from unextractable dependencies with performance implications which makes me think that forcing SOLID might be a bad idea here. It's a trade-off
If you have a service layer, it would make more sense to use it to do the conversion or make it delegate the task to the converter.
Ideally, converters should be just converters : a mapper object, not a service.
Now if the logic is not too complex and converters are not reusable, you may mix service processing with mapping processing and in this case you could replace the Converter prefix by Service.
And also it would seem nicer if only the services communicate with the repository.
Otherwise layers become blur and the design messy : we don't know really any longer who invokes who.
I would do things in this way :
controller -> service -> converter
-> repository
or a service that performs itself the conversion (it conversion is not too complex and it is not reusable) :
controller -> service -> repository
Now to be honest I hate DTO as these are just data duplicates.
I introduce them only as the client requirements in terms of information differ from the entity representation and that it makes really clearer to have a custom class (that in this case is not a duplicate).
personally, converters should be between your controllers and services, the only things DTOs should worry about is the data in your service layer and how which information to expose to your controllers.
controllers <-> converters <-> services ...
in your case, you can make use of JPA to populate roles of your users at the persistence layer.
I suggest that you just use Mapstruct to solve this kind of entity to dto convertion issue that you are facing. Through an annotation processor the mappings from dto to entity and vice versa are generated automatically and you just have to inject a reference from your mapper to your controller just like you normally would do with your repositories (#Autowired).
You can also check out this example to see if it fit your needs.
That's the way I'd likely do it. The way I'd conceptualize it is that the User converter is responsible for user / user dto conversions, and as such it rightly shouldn't be responsible for role / role dto conversion. In your case, the role repository is acting implicitly as a role converter that the user converter is delegating to. Maybe someone with more in-depth knowledge of SOLID can correct me if I'm wrong, but personally I feel like that checks out.
The one hesitation I would have, though, would be the fact that you're tying the notion of conversion to a DB operation which isn't necessarily intuitive, and I'd want to be careful that months or years into the future some developer doesn't inadvertently grab the component and use it without understanding the performance considerations (assuming you're developing on a larger project, anyways). I might consider creating some decorator class around the role repository that incorporates caching logic.
I think the way to do it cleanly is to include a Role DTO that you convert to the RoleEntity. I might use a simplified User DTO in case that it is read only. For example, in case of unprivileged access.
To expand your example
public class UserDto {
private Long id;
private String name;
private RoleDto role;
}
with the Role DTO as
public class RoleDto {
private Long id;
private String roleName;
}
And the JSON
{
id: 1,
name: "user",
role: {
id: 123,
roleName: "manager"
}
Then you can convert the RoleDto to RoleEntity while converting the User in your UserConverter and remove the repository access.
Instead of creating separate convertor clas, you can give that responsibility to Entity class itself.
public class UserEntity {
// properties
public static UserEntity valueOf(UserDTO userDTO) {
UserEntity userEntity = new UserEntity();
// set values;
return userEntity;
}
public UserDTO toDto() {
UserDTO userDTO = new UserDTO();
// set values
return userDTO;
}
}
Usage;
UserEntity userEntity = UserEntity.valueOf(userDTO);
UserDTO userDTO = userEntity.toDto();
In this way you have your domain in one place. You can use Spring BeanUtils to set properties.
You can do the same for RoleEntity and decide whether to lazy/eager load when loading UserEntity using ORM tool.

Java server side validation for HTML and other invalid inputs

How can I prevent user from entering HTML or Java script tags in input type in Spring MVC? There should be a server side validation. I am working on a project with thousands of JSPs and controllers. How can I do this?
If you want a server side solution, you could implement a redirect filter that eliminates everything that contains javascript tags and javascript code. Another way is to check the input values in the controller's method that is associated with it.
You probably have to redesign a few things. First, you should always validate the user input twice: once client-side, once server-side.
Thus, you will need to validate the user input in your JavaScript code (using a Regexp probably), and to validate it again in your Java code.
If your application follow the usual design patterns, your controller receives a DTO as a parameter to the entry-point. There you can use the #Valid annotation and add all the necessary rules on the fields of your DTO (using javax.validation annotations).
While there may be many possible answers, one of them is using JSR 303 validator framework.
You can include hibernate validator to use JSR 303 framework.
First step is applying different type of constraint on your class. For example
example taken from : Hibernate Validator - Reference - 1.2. Applying constraints
package org.hibernate.validator.referenceguide.chapter01;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class Car {
#NotNull //manufacturer must never be null
private String manufacturer;
#NotNull
#Size(min = 2, max = 14) //licensePlate must never be null and must be between 2 and 14 characters long
private String licensePlate;
#Min(2)
private int seatCount; //seatCount must be at least 2
//getters and setters ...
}
Now in your controller, use #Valid annotation to validate your car object and also pass a BindingResult parameter, that will validate whether this object is valid or not
#Controller
#RequestMapping("/car")
public class CarController {
#RequestMapping(value = "/newcar", method = RequestMethod.POST)
public String addCustomer(#Valid Car car, BindingResult result) {
if (result.hasErrors()) {
//car data is not valid, enter data again
return "AddNewCar.jsp";
} else {
//save car logic here
return "CarSavedSuccessfully.jsp";
}
}
}

Best way to convert a JPA entity to REST representations using JAX-RS and Jackson

I'm looking for a way to export some JPA entities to a REST API, but instead of sending the whole entity every time I want to share just some specific fields depending of the entry point. Here's a small example:
Say we have an Author class with few fields:
#Entity
public class Author implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = SEQUENCE)
private Long id;
#NotNull
#Size(min = 1, message = "{required.field}")
private String name;
#NotNull
#Size(min = 1, message = "{required.field}")
private String country;
private LocalDate birthDate;
// getters and setters
}
And say we have this REST service (just two methods):
#Path("authors")
public class AuthorREST {
#Inject
private AuthorBC bc;
#GET
#Produces("application/json")
public List<Author> find(#QueryParam("q") String query) throws Exception {
List<Author> result;
if (Strings.isEmpty(query)) {
result = bc.findAll();
} else {
result = bc.find(query);
}
return result;
}
#GET
#Path("{id}")
#Produces("application/json")
public Author load(#PathParam("id") Long id) throws Exception {
Author result = bc.load(id);
if (result == null) {
throw new NotFoundException();
}
return result;
}
}
Now, this way, I'll always have the 4 fields when my API is called.
I understand that if I use Jackson I can set an #JsonIgnore to fields I want to ignore, and they will always be ignored.
But what if I want that, in some cases, my whole entity is returned by one service, and in other service (or other method in the same service), only 2 or 3 fields are returned?
Is there a way to do it?
#JsonView and mix-in
You already know you can use annotations such as #JsonIgnore and #JsonIgnoreProperties to make Jackson ignore some properties.
You also could check the #JsonView annotation. For some details on how to use #JsonView with JAX-RS, have a look here.
If modifying the JPA entities is not an option, consider mix-in annotations as described in this answer.
Data Transfer Object
Data Transfer Object (DTO) is a pattern that was created with a very well defined purpose: transfer data to remote interfaces, just like webservices. This pattern fits very well in REST APIs and using DTOs you'll have more flexibility in the long run. You can have tailored classes for your needs, once the REST resource representations don't need to have the same attributes as the persistence objects.
To avoid boilerplate code, you can use mapping frameworks such as MapStruct to map your REST API DTOs from/to your persistence objects.
For details on the benefits of using DTOs, check the following answers:
Why you should use DTOs in your REST API
Using tailored classes of request and response
To give better names to your DTOs, check the following answer:
Giving meaningful names to your DTOs
If you want to decouple the parsing from your JPA entities and return only certain attributes you can always use Mixins for this purpose.
http://www.cowtowncoder.com/blog/archives/2009/08/entry_305.html
https://github.com/FasterXML/jackson-docs/wiki/JacksonMixInAnnotations
One more thing. If you want things to be dynamic in one service to return one representation in another to return another representation. Your option is to write a custom JSON serializer!
Check this post for how to create a customer serializer:
How do I use a custom Serializer with Jackson?
For myself I found it quite suitable to use #JsonView annotation. So you can define fields to be rendered in specific view. You can find more info here http://wiki.fasterxml.com/JacksonJsonViews
I think you can write a custom MessageBodyWriter using Jersey framework and you can control the response payload the way you want. Here you have to write few lines of code in-order to manage the response payload. For more information please visit https://jersey.java.net/documentation/latest/message-body-workers.html#d0e6826
I would use Spring Data REST and then use the ApiModel annotation to hide the attributes you do not want exposed.

Categories

Resources