How to handling bad request in REST service - java

I have a rest service that receives an object like this:
public class Item implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String ID;
private String name;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date insertDate;
//getter setter
}
My controller has a saveItem method of this type:
#RequestMapping(value = "/item", method = RequestMethod.POST, produces = "application/json")
public Object saveItem(#RequestBody(required = false) Item item) {
if (item == null) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
//code..
return new ResponseEntity<>(HttpStatus.OK);
}
when an incorrect date format arrives, an error is generated that is not handled by me because it does not enter the if. Why does this happen? How should it be managed?
The problem occurred even without the #JsonFormat annotation

I suppose you are using spring boot. You can use ControllerAdvice in order to define response in case of exceptions. You can define multiple handlers as multiple methods annotated with ExceptionHandler.
#Slf4j
#ControllerAdvice
public class RestControllerAdvice {
#ExceptionHandler(InvalidArgumentException.class)
public ResponseEntity<ResponseDto<Object>> handleException(Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity<>(<someBody>, HttpStatus. BAD_REQUEST);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<ResponseDto<Object>> handleException2(Exception exception) {
log.error(exception.getMessage(), exception);
return new ResponseEntity<>(<someBody>, HttpStatus. INTERNAL_SERVER_ERROR);
}
}

Related

Intercept request to create RequestBody

I have a base CreateRequest class which I inherit in a couple of children, each responsible for some custom validations:
Base:
public class CreateEventRequest {
#NotEmpty
private String name;
#NotNull
#JsonProperty
private Boolean isPrivate;
}
Child:
public class CreateRegularEventRequest extends CreateEventRequest {
#NotNull
#Future
private LocalDateTime startTime;
#NotNull
#Future
private LocalDateTime endTime;
public LocalDateTime getStartTime() {
return startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
}
In order to take advantage of the validations, I've tried this:
#PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CreateEventResponse> createEvent(HttpEntity<String> httpEntity,
#AuthenticationPrincipal SecuredUser user) {
try {
CreateEventRequest eventRequest = eventRequestFactory.getEventRequestString(httpEntity.getBody());
Set<ConstraintViolation<CreateEventRequest>> violations = validator.validate(eventRequest);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
return new ResponseEntity<>(this.service.createEvent(eventRequest, user), HttpStatus.CREATED);
} catch (ConstraintViolationException e) {
throw e;
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
My factory is:
public CreateEventRequest getEventRequestString(String json) {
try {
String eventTypeRaw = new ObjectMapper().readTree(json)
.get("event_type").asText();
if (!StringUtils.isEmpty(eventTypeRaw)) {
EventType eventType = EventType.valueOf(eventTypeRaw);
if (EventType.REGULAR.equals(eventType)) {
return objectMapper.readValue(json, CreateRegularEventRequest.class);
} else if (EventType.RECURRING.equals(eventType)) {
return objectMapper.readValue(json, CreateRecurringEventRequest.class);
}
}
return null;
} catch (JsonProcessingException e) {
return null;
}
}
This seems really hacky to me and do not scale for future Parent-Child relation, my question is, is there a way to intercept this request to create this child classes and pass it to the controller or some built-in validations to handle this scenario?
Thanks!
Did yo try:
public ResponseEntity<CreateEventResponse> createEvent( #Valid #RequestBody CreateRegularEventRequest reqBody ...) {
..
all the mapping and validation are done automatically

Not Able to Successfully USE Validations in Spring REST Webservice

I am trying to apply validations on my SPRING REST-API but i am getting this exception:
Apr 10, 2020 12:05:26 PM org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver doResolveHandlerMethodExceptionWARNING: Failed to invoke #ExceptionHandler method: public com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation com.luv2code.springdemo.exceptionhandling.GlobalExceptionHandler.processValidationError(org.springframework.web.bind.MethodArgumentNotValidException)org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class com.luv2code.springdemo.exceptionhandling.RestFieldErrorValidation at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:226)
Entity Class:
#Entity#Table(name="customer")
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
#NotNull(message = "Firstname is necessary")
#Size(min=1,message="This field is required")
private String firstName;
#Column(name="last_name")
#NotNull(message = "Lastname is necessary")
#Size(min=1,message="This field is required")
private String lastName;
#Column(name="email")
private String email;
// getters and setters
}
FieldValidation Handler classes:
public class RestFieldError {
private String field;
private String message;
public RestFieldError() {
}
// getters and setters
}
and
public class RestFieldErrorValidation {
private List<RestFieldError> fieldErrors = new ArrayList<>();
public RestFieldErrorValidation() {
}
public void addFieldError(String path, String message) {
RestFieldError error = new RestFieldError(path, message);
fieldErrors.add(error);
}
}
RestController Code:
#RestController
#RequestMapping("/api")
public class CustomerRestController {
// autowire the CustomerService
#Autowired
private CustomerService customerService;
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
System.out.println("Entered init binder");
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
// add the mapping for POST/customers (add a new customer)
#PostMapping("/customers")
#ResponseBody
public Customer addCustomer(#Valid #RequestBody Customer theCustomer) {
System.out.println("theCustomer :"+theCustomer.getFirstName());
theCustomer.setId(0);
customerService.saveCustomer(theCustomer);
return theCustomer;
}
}
Exception handler Class:
#ControllerAdvice
public class GlobalExceptionHandler {
// Adding Validation Support on REST APIs--------------------------------------------------------->
private MessageSource messageSource;
#Autowired
public GlobalExceptionHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public RestFieldErrorValidation processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private RestFieldErrorValidation processFieldErrors(List<FieldError> fieldErrors) {
RestFieldErrorValidation dto = new RestFieldErrorValidation();
for (FieldError fieldError: fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getField(), localizedErrorMessage);
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
//If the message was not found, return the most accurate field error code instead.
//You can remove this check if you prefer to get the default error message.
if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
String[] fieldErrorCodes = fieldError.getCodes();
localizedErrorMessage = fieldErrorCodes[0];
}
return localizedErrorMessage;
}
}
Here is the google drive link of the project if you can check the code:
https://drive.google.com/open?id=1QSFVMi3adHGkc7BqXsqAY0P_tO2UfT2I
Here is the Article that i followed:
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
I'm assuming you are using plain Spring here, not Spring Boot.
The question is: To what exactly do you want to convert your RestFieldErrorValidation object? XML? JSON?
For either, you need an appropriate third-party library on your classpath, so Spring can do the conversion automatically.
In the case of JSON, you might want to add this dependency to your project.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>

Ignore JSON fields for POST request but should not ignore for GET request

I am new to the Spring Boot framework. I have a model like below :
public class Process{
private String processId;
private ProcessStatus status;
private hostname;
private errorMessage;
}
Post calls from the controller:
#RequestMapping(value = "/process", method = RequestMethod.POST,
consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public HttpStatus updateProcess (#RequestBody Process process)
{
try {
processService.updateProcess(process);
return HttpStatus.OK;
} catch (Exception e)
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,e,
e);
}
}
Get call for the process :
#RequestMapping(value = "/process", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public Process getProcess (#RequestParam("processId") String processId)
{
try {
return processService.getProcess(processId);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
e);
}
}
So post is being performed by component-A who is responsible to update the information about the process.
Get is being done by component-B to get the existing process.
Question: How can i hide hostname and errorMessage for the get and but not for the post. When I do #JsonIgnore off course it will ignore for both requests.
You can use JsonView feature. You can define few different levels:
public class Views {
public static class Public {
}
public static class Internal extends Public {
}
}
Now, you need to annotate your POJO attributes:
public class Process{
#JsonView(Views.Public.class)
private String processId;
#JsonView(Views.Public.class)
private ProcessStatus status;
#JsonView(Views.Internal.class)
private String hostname;
#JsonView(Views.Internal.class)
private String errorMessage;
}
When you already have this you need to annotate API methods. GET:
#RequestMapping(value = "/process", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_UTF8_VALUE })
#JsonView(Views.Public.class)
public Process getProcess (#RequestParam("processId") String processId)
And for the POST:
#RequestMapping(value = "/process", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
#JsonView(Views.Internal.class)
public HttpStatus updateProcess (#RequestBody Process process)
Of course, you can pick better names for views. Above is just an example usage after reading linked article. I did not test it but you should get general idea.
Simplest way is to use another DTO(data transfer object) class which does not contain the fields which you don't want to send back. In your case this would mean to create a ProcesDTO class, which would look like this:
public class ProcessDTO {
private String processId;
private ProcessStatus status;
private hostname;
}
Also, this implies to do the mapping between Process and ProcessDTO, which can be done by hand or can be done by using some kind of library, like org.modelmapper.
Assuming that Process is some kind of entity class, it is not really recommended anyway to use them directly to send back JSON data.

Ignore fields from Java object dynamically while sending as JSON from Spring MVC

I have model class like this, for hibernate
#Entity
#Table(name = "user", catalog = "userdb")
#JsonIgnoreProperties(ignoreUnknown = true)
public class User implements java.io.Serializable {
private Integer userId;
private String userName;
private String emailId;
private String encryptedPwd;
private String createdBy;
private String updatedBy;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "UserId", unique = true, nullable = false)
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
#Column(name = "UserName", length = 100)
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
#Column(name = "EmailId", nullable = false, length = 45)
public String getEmailId() {
return this.emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
#Column(name = "EncryptedPwd", length = 100)
public String getEncryptedPwd() {
return this.encryptedPwd;
}
public void setEncryptedPwd(String encryptedPwd) {
this.encryptedPwd = encryptedPwd;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
#Column(name = "UpdatedBy", length = 100)
public String getUpdatedBy() {
return this.updatedBy;
}
public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}
}
In Spring MVC controller, using DAO, I am able to get the object. and returning as JSON Object.
#Controller
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/getUser/{userId}", method = RequestMethod.GET)
#ResponseBody
public User getUser(#PathVariable Integer userId) throws Exception {
User user = userService.get(userId);
user.setCreatedBy(null);
user.setUpdatedBy(null);
return user;
}
}
View part is done using AngularJS, so it will get JSON like this
{
"userId" :2,
"userName" : "john",
"emailId" : "john#gmail.com",
"encryptedPwd" : "Co7Fwd1fXYk=",
"createdBy" : null,
"updatedBy" : null
}
If I don't want to set encrypted Password, I will set that field also as null.
But I don't want like this, I dont want to send all fields to client side. If I dont want password, updatedby, createdby fields to send, My result JSON should be like
{
"userId" :2,
"userName" : "john",
"emailId" : "john#gmail.com"
}
The list of fields which I don't want to send to client coming from other database table. So it will change based on the user who is logged in. How can I do that?
I hope You got my question.
Add the #JsonIgnoreProperties("fieldname") annotation to your POJO.
Or you can use #JsonIgnore before the name of the field you want to ignore while deserializing JSON. Example:
#JsonIgnore
#JsonProperty(value = "user_password")
public String getUserPassword() {
return userPassword;
}
GitHub example
Can I do it dynamically?
Create view class:
public class View {
static class Public { }
static class ExtendedPublic extends Public { }
static class Internal extends ExtendedPublic { }
}
Annotate you model
#Document
public class User {
#Id
#JsonView(View.Public.class)
private String id;
#JsonView(View.Internal.class)
private String email;
#JsonView(View.Public.class)
private String name;
#JsonView(View.Public.class)
private Instant createdAt = Instant.now();
// getters/setters
}
Specify the view class in your controller
#RequestMapping("/user/{email}")
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping(method = RequestMethod.GET)
#JsonView(View.Internal.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
}
}
Data example:
{"id":"5aa2496df863482dc4da2067","name":"test","createdAt":"2018-03-10T09:35:31.050353800Z"}
UPD: keep in mind that it's not best practice to use entity in response. Better use different DTO for each case and fill them using modelmapper
I know I'm a bit late to the party, but I actually ran into this as well a few months back. All of the available solutions weren't very appealing to me (mixins? ugh!), so I ended up creating a new library to make this process cleaner. It's available here if anyone would like to try it out: https://github.com/monitorjbl/spring-json-view.
The basic usage is pretty simple, you use the JsonView object in your controller methods like so:
import com.monitorjbl.json.JsonView;
import static com.monitorjbl.json.Match.match;
#RequestMapping(method = RequestMethod.GET, value = "/myObject")
#ResponseBody
public void getMyObjects() {
//get a list of the objects
List<MyObject> list = myObjectService.list();
//exclude expensive field
JsonView.with(list).onClass(MyObject.class, match().exclude("contains"));
}
You can also use it outside of Spring:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import static com.monitorjbl.json.Match.match;
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(JsonView.class, new JsonViewSerializer());
mapper.registerModule(module);
mapper.writeValueAsString(JsonView.with(list)
.onClass(MyObject.class, match()
.exclude("contains"))
.onClass(MySmallObject.class, match()
.exclude("id"));
Yes, you can specify which fields are serialized as JSON response and which to ignore.
This is what you need to do to implement Dynamically ignore properties.
1) First, you need to add #JsonFilter from com.fasterxml.jackson.annotation.JsonFilter on your entity class as.
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("SomeBeanFilter")
public class SomeBean {
private String field1;
private String field2;
private String field3;
// getters/setters
}
2) Then in your controller, you have to add create the MappingJacksonValue object and set filters on it and in the end, you have to return this object.
import java.util.Arrays;
import java.util.List;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
#RestController
public class FilteringController {
// Here i want to ignore all properties except field1,field2.
#GetMapping("/ignoreProperties")
public MappingJacksonValue retrieveSomeBean() {
SomeBean someBean = new SomeBean("value1", "value2", "value3");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
FilterProvider filters = new SimpleFilterProvider().addFilter("SomeBeanFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(someBean);
mapping.setFilters(filters);
return mapping;
}
}
This is what you will get in response:
{
field1:"value1",
field2:"value2"
}
instead of this:
{
field1:"value1",
field2:"value2",
field3:"value3"
}
Here you can see it ignores other properties(field3 in this case) in response except for property field1 and field2.
Hope this helps.
We can do this by setting access to JsonProperty.Access.WRITE_ONLY while declaring the property.
#JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
#SerializedName("password")
private String password;
Add #JsonInclude(JsonInclude.Include.NON_NULL) (forces Jackson to serialize null values) to the class as well as #JsonIgnore to the password field.
You could of course set #JsonIgnore on createdBy and updatedBy as well if you always want to ignore then and not just in this specific case.
UPDATE
In the event that you do not want to add the annotation to the POJO itself, a great option is Jackson's Mixin Annotations. Check out the documentation
I've solved using only #JsonIgnore like #kryger has suggested.
So your getter will become:
#JsonIgnore
public String getEncryptedPwd() {
return this.encryptedPwd;
}
You can set #JsonIgnore of course on field, setter or getter like described here.
And, if you want to protect encrypted password only on serialization side (e.g. when you need to login your users), add this #JsonProperty annotation to your field:
#JsonProperty(access = Access.WRITE_ONLY)
private String encryptedPwd;
More info here.
If I were you and wanted to do so, I wouldn't use my User entity in Controller layer.Instead I create and use UserDto (Data transfer object) to communicate with business(Service) layer and Controller.
You can use Apache BeanUtils(copyProperties method) to copy data from User entity to UserDto.
I have created a JsonUtil which can be used to ignore fields at runtime while giving a response.
Example Usage :
First argument should be any POJO class (Student) and ignoreFields is comma seperated fields you want to ignore in response.
Student st = new Student();
createJsonIgnoreFields(st,"firstname,age");
import java.util.logging.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
public class JsonUtil {
public static String createJsonIgnoreFields(Object object, String ignoreFields) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(Object.class, JsonPropertyFilterMixIn.class);
String[] ignoreFieldsArray = ignoreFields.split(",");
FilterProvider filters = new SimpleFilterProvider()
.addFilter("filter properties by field names",
SimpleBeanPropertyFilter.serializeAllExcept(ignoreFieldsArray));
ObjectWriter writer = mapper.writer().withFilters(filters);
return writer.writeValueAsString(object);
} catch (Exception e) {
//handle exception here
}
return "";
}
public static String createJson(Object object) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
return writer.writeValueAsString(object);
}catch (Exception e) {
//handle exception here
}
return "";
}
}
I've found a solution for me with Spring and jackson
First specify the filter name in the entity
#Entity
#Table(name = "SECTEUR")
#JsonFilter(ModelJsonFilters.SECTEUR_FILTER)
public class Secteur implements Serializable {
/** Serial UID */
private static final long serialVersionUID = 5697181222899184767L;
/**
* Unique ID
*/
#Id
#JsonView(View.SecteurWithoutChildrens.class)
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JsonView(View.SecteurWithoutChildrens.class)
#Column(name = "code", nullable = false, length = 35)
private String code;
/**
* Identifiant du secteur parent
*/
#JsonView(View.SecteurWithoutChildrens.class)
#Column(name = "id_parent")
private Long idParent;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "id_parent")
private List<Secteur> secteursEnfants = new ArrayList<>(0);
}
Then you can see the constants filters names class with the default FilterProvider used in spring configuration
public class ModelJsonFilters {
public final static String SECTEUR_FILTER = "SecteurFilter";
public final static String APPLICATION_FILTER = "ApplicationFilter";
public final static String SERVICE_FILTER = "ServiceFilter";
public final static String UTILISATEUR_FILTER = "UtilisateurFilter";
public static SimpleFilterProvider getDefaultFilters() {
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAll();
return new SimpleFilterProvider().setDefaultFilter(theFilter);
}
}
Spring configuration :
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "fr.sodebo")
public class ApiRootConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private EntityManagerFactory entityManagerFactory;
/**
* config qui permet d'éviter les "Lazy loading Error" au moment de la
* conversion json par jackson pour les retours des services REST<br>
* on permet à jackson d'acceder à sessionFactory pour charger ce dont il a
* besoin
*/
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
// config d'hibernate pour la conversion json
mapper.registerModule(getConfiguredHibernateModule());//
// inscrit les filtres json
subscribeFiltersInMapper(mapper);
// config du comportement de json views
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
converter.setObjectMapper(mapper);
converters.add(converter);
}
/**
* config d'hibernate pour la conversion json
*
* #return Hibernate5Module
*/
private Hibernate5Module getConfiguredHibernateModule() {
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
Hibernate5Module module = new Hibernate5Module(sessionFactory);
module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
return module;
}
/**
* inscrit les filtres json
*
* #param mapper
*/
private void subscribeFiltersInMapper(ObjectMapper mapper) {
mapper.setFilterProvider(ModelJsonFilters.getDefaultFilters());
}
}
Endly I can specify a specific filter in restConstoller when i need....
#RequestMapping(value = "/{id}/droits/", method = RequestMethod.GET)
public MappingJacksonValue getListDroits(#PathVariable long id) {
LOGGER.debug("Get all droits of user with id {}", id);
List<Droit> droits = utilisateurService.findDroitsDeUtilisateur(id);
MappingJacksonValue value;
UtilisateurWithSecteurs utilisateurWithSecteurs = droitsUtilisateur.fillLists(droits).get(id);
value = new MappingJacksonValue(utilisateurWithSecteurs);
FilterProvider filters = ModelJsonFilters.getDefaultFilters().addFilter(ModelJsonFilters.SECTEUR_FILTER, SimpleBeanPropertyFilter.serializeAllExcept("secteursEnfants")).addFilter(ModelJsonFilters.APPLICATION_FILTER,
SimpleBeanPropertyFilter.serializeAllExcept("services"));
value.setFilters(filters);
return value;
}
Place #JsonIgnore on the field or its getter, or create a custom dto
#JsonIgnore
private String encryptedPwd;
or as mentioned above by ceekay annotate it with #JsonProperty where access attribute is set to write only
#JsonProperty( value = "password", access = JsonProperty.Access.WRITE_ONLY)
private String encryptedPwd;
Can I do it dynamically?
Yes, you can use a combination of Jackson's PropertyFilter and mixins.
Explanation
Jackson has a PropertyFilter interface to implement a filter to ignore fields dynamically. The problem is that filter has to be defined on the DTO/POJO class using the #JsonFilter annotation.
To avoid adding a #JsonFilter on class we can use ObjectMapper's addMixIn method to "dynamically" add this annotation (and leave our DTO/POJO classes as is).
Code example
Here is my implementation of the idea provided above. We can call toJson() with two arguments: (1) object to be serialized and (2) lambda (Java's Predicate) to be used in PropertyFilter:
public class JsonService {
public String toJson(Object object, Predicate<PropertyWriter> filter) {
ObjectMapper mapper = new ObjectMapper();
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("DynamicFilter", new DynamicFilter(filter));
mapper.setFilterProvider(filterProvider);
mapper.addMixIn(object.getClass(), DynamicFilterMixin.class);
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new MyException(e);
}
}
private static final class DynamicFilter extends SimpleBeanPropertyFilter {
private Predicate<PropertyWriter> filter;
private DynamicFilter(Predicate<PropertyWriter> filter) {
this.filter = filter;
}
protected boolean include(BeanPropertyWriter writer) {
return include((PropertyWriter) writer);
}
protected boolean include(PropertyWriter writer) {
return filter.test(writer);
}
}
#JsonFilter("DynamicFilter")
private interface DynamicFilterMixin {
}
}
Now we can call toJson and filter fields during a serialization:
Filtering by name
new JsonService().toJson(object, w -> !w.getName().equals("fieldNameToBeIgnored"));
Filtering by annotation (on the field)
new JsonService().toJson(object, w -> w.getAnnotation(MyAnnotation.class) == null);
Unit tests
Here are the unit tests for the class above:
public class JsonServiceTest {
private JsonService jsonService = new JsonService();
#Test
public void withoutFiltering() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> true);
assertEquals("{\"myString\":\"stringValue\",\"myInteger\":10,\"myBoolean\":true}", json);
}
#Test
public void filteredByFieldName() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> !w.getName().equals("myString"));
assertEquals("{\"myInteger\":10,\"myBoolean\":true}", json);
}
#Test
public void filteredByAnnotation() {
MyObject object = getObject();
String json = jsonService.toJson(object, w -> w.getAnnotation(Deprecated.class) == null);
assertEquals("{\"myString\":\"stringValue\",\"myInteger\":10}", json);
}
private MyObject getObject() {
MyObject object = new MyObject();
object.myString = "stringValue";
object.myInteger = 10;
object.myBoolean = true;
return object;
}
private static class MyObject {
private String myString;
private int myInteger;
#Deprecated
private boolean myBoolean;
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
public int getMyInteger() {
return myInteger;
}
public void setMyInteger(int myInteger) {
this.myInteger = myInteger;
}
public boolean isMyBoolean() {
return myBoolean;
}
public void setMyBoolean(boolean myBoolean) {
this.myBoolean = myBoolean;
}
}
}
Would not creating a UserJsonResponse class and populating with the wanted fields be a cleaner solution?
Returning directly a JSON seems a great solution when you want to give all the model back. Otherwise it just gets messy.
In the future, for example you might want to have a JSON field that does not match any Model field and then you're in a bigger trouble.
This is a clean utility tool for the above answer :
#GetMapping(value = "/my-url")
public #ResponseBody
MappingJacksonValue getMyBean() {
List<MyBean> myBeans = Service.findAll();
MappingJacksonValue mappingValue = MappingFilterUtils.applyFilter(myBeans, MappingFilterUtils.JsonFilterMode.EXCLUDE_FIELD_MODE, "MyFilterName", "myBiggerObject.mySmallerObject.mySmallestObject");
return mappingValue;
}
//AND THE UTILITY CLASS
public class MappingFilterUtils {
public enum JsonFilterMode {
INCLUDE_FIELD_MODE, EXCLUDE_FIELD_MODE
}
public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final String... fields) {
if (fields == null || fields.length == 0) {
throw new IllegalArgumentException("You should pass at least one field");
}
return applyFilter(object, mode, filterName, new HashSet<>(Arrays.asList(fields)));
}
public static MappingJacksonValue applyFilter(Object object, final JsonFilterMode mode, final String filterName, final Set<String> fields) {
if (fields == null || fields.isEmpty()) {
throw new IllegalArgumentException("You should pass at least one field");
}
SimpleBeanPropertyFilter filter = null;
switch (mode) {
case EXCLUDE_FIELD_MODE:
filter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
break;
case INCLUDE_FIELD_MODE:
filter = SimpleBeanPropertyFilter.filterOutAllExcept(fields);
break;
}
FilterProvider filters = new SimpleFilterProvider().addFilter(filterName, filter);
MappingJacksonValue mapping = new MappingJacksonValue(object);
mapping.setFilters(filters);
return mapping;
}
}
To acheive dynamic filtering follow the link - https://iamvickyav.medium.com/spring-boot-dynamically-ignore-fields-while-converting-java-object-to-json-e8d642088f55
Add the #JsonFilter("Filter name") annotation to the model class.
Inside the controller function add the code:-
SimpleBeanPropertyFilter simpleBeanPropertyFilter =
SimpleBeanPropertyFilter.serializeAllExcept("id", "dob");
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("Filter name", simpleBeanPropertyFilter);
List<User> userList = userService.getAllUsers();
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(userList);
mappingJacksonValue.setFilters(filterProvider);
return mappingJacksonValue;
make sure the return type is MappingJacksonValue.
Hi I have achieved dynamic filtering by using Gson library like in the below:
JsonObject jsonObj = new Gson().fromJson(mapper.writeValueAsString(sampleObject), JsonObject.class);
jsonObj.remove("someProperty");
String data = new Gson().toJson(jsonObj);
In your entity class add #JsonInclude(JsonInclude.Include.NON_NULL) annotation to resolve the problem
it will look like
#Entity
#JsonInclude(JsonInclude.Include.NON_NULL)

Getting JsonMappingException while sending data to view

I am trying to show DB data to my webpage.
I have made following code when GET request to the #RequestMapping(value = "/api/binder").
but when get request came to this method it will fetch data (I have print on console and display well) but it doesn't map to my Java Script Ajax call, it's showing me an error.
Following is my code for to fetch data :
#Autowired
IBinderViewRepository repository;
#RequestMapping(method= RequestMethod.GET)
public #ResponseBody
List<BinderResponse> getBinders(){
List<BinderView> binders = repository.getBinders();
List<BinderResponse> responses = new ArrayList<>();
ModelMapper mapper = Mapper.getInstance();
for(int i = 0; i < binders.size(); i++){
System.out.println("In Loop");
BinderResponse response = mapper.map(binders.get(i),BinderResponse.class);
System.out.println("Data :: " + response.getBinderName());
responses.add(response);
}
return responses;
}
but it shows me following error :
HTTP Status 500 - Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"])
Here is ajax call from knockout js :
ajax.get('api/binder').done(function(response){ ... }
Here BinderView and BinderResponse have same fields :
private String binderName;
private String binderAddress1;
and getter setter as well in both.
and repository.genBinders() method bring data from DB.
Here is insert method and works fine for me :
#RequestMapping(method= RequestMethod.POST,consumes = "application/json")
public #ResponseBody
IWebApiResponse addBinder(#RequestBody AddBinderForm binder){
.....
}
Shall I have to put any json annotation on my BinderResponse class ?
I don't understand where am i wrong ?Anyone pleas guide me.
UPDATE :
public class BinderResponse extends WebApiResponseBase {
private String binderName;
private String binderAddress1;
public String getBinderName() {
return binderName;
}
public void setBinderName(String binderName) {
this.binderName = binderName;
}
public String getBinderAddress1() {
return binderAddress1;
}
public void setBinderAddress1(String binderAddress1) {
this.binderAddress1 = binderAddress1;
}
}
BinderView :
public class BinderView extends BaseView {
private String binderName;
private String binderAddress1;
public String getBinderName() {
return binderName;
}
public void setBinderName(String binderName) {
this.binderName = binderName;
}
public String getBinderAddress1() {
return binderAddress1;
}
public void setBinderAddress1(String binderAddress1) {
this.binderAddress1 = binderAddress1;
}
}
In console it prints data / BinderName :
In Loop
Data :: ada
In Loop
Data :: tya
New Update :
Here is BaseView :
#MappedSuperclass
public abstract class BaseView implements IEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="id")
private long id;
public long getId() {
return id;
}
public void setId(long id) {
if (this.id != 0 && this.id != id) {
throw new IllegalStateException(
"The ID must not be changed after it is set.");
}
this.id = id;
}
}
and In IEntity :
public interface IEntity extends Serializable {
long getId();
void setId(long id);
}
WebApiResponseBase :
public class WebApiResponseBase implements IWebApiResponse {
private String _uri;
#Override
public String getUri() {
return _uri == null ? "" : _uri;
}
#Override
public void setUri(String uri) {
_uri = uri;
}
}
Jackson, by default, serializes an object's whole inheritance hierarchy, ie. the parent class fields as well. In the case of
public class BinderResponse extends WebApiResponseBase {
it seems like
Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.ngl.dto.outgoing.BinderResponse["valid"])
Jackson tries to serialize a field called valid from a getter called isValid (which is a conventional bean property name). The getter method, however, seems to throw a NullPointerException for whatever reason.
If you want Jackson to ignore it, you can annotate the getter with #JsonIgnore or your class with #JsonIgnoreProperties and specify the property name, ie. valid.
In my case when I used #JsonIgnore the exception has been gone but the problem was it couldn't receive that value from API Request anymore and Spring ignored it (obviously because of #JsonIgnore) So I investigated about the issue and figured out that the problem was the getter and setter.
I had the Integer property while my getter was int. So when I changed the getter to Integer my problem solved and error's gone.
private Integer purchaseId;
#JsonIgnore
public int getPurchaseId() {
return purchaseId;
}
public void setPurchaseId(int purchaseId) {
this.purchaseId = purchaseId;
}
Changed to :
private Integer purchaseId;
public Integer getPurchaseId() {
return purchaseId;
}
public void setPurchaseId(Integer purchaseId) {
this.purchaseId = purchaseId;
}
#Column(name="createddate")
private Date createdDate;
#Transient
private String formatedCreatedDate;
public String getFormatedCreatedDate() {
DateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
return dateFormat.format(this.getCreatedDate());
}
It throws the same exception because here may be null by calling getCreatedDate() value come so it can't format null date so keep null check here like:
Solution
public String getFormatedCreatedDate() {
DateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
Date createDdate=this.getCreatedDate();
if(createDdate!=null){
return dateFormat.format(createDdate);
}
return "-";
}

Categories