I'm writing a simple REST service which expects a POST containing a Json.
The service should refuse any Json not containing exactly the keys it expects.
I'm using JAX-RS on a JBoss EAP 7.1 and have been unable to do this. Code example:
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/")
#Stateless
#Produces("text/plain")
public class TestAccess
{
#POST
#Path("test/")
#Consumes(MediaType.APPLICATION_JSON)
public String consumeJson(TestClass c)
{
return c.toString();
}
}
public class TestClass
{
public String first;
public String second;
public String toString()
{
return (first + ", " + second);
}
}
Previously I wrote consumeJson to expect expect a JsonObject and parsed that using a Jackson ObjectMapper. Doing this resulted in an error when receiving a Json missing keys. I think the way I'm doing it now is a "cleaner" way to do it as now the parameter list clearly shows what kind of Object the Json should describe. However, I don't want to check every single field first thing in the method nor do I want to add a isValid() method to every Object I get this way.
The example {"first" : "contentOfFirst"} returns "contentOfFirst, null" instead of failing.
EDIT: To clarify, I attempted something like this:
import javax.validation.constraints.NotNull;
public class TestClass
{
#NotNull
public String first;
#NotNull
public String second;
public String toString()
{
return (first + ", " + second);
}
}
This did not change the outcome, instead of failing (as it was supposed to) the POST still got the response "contentOfFirst, null". I'm aware these annotations still need a validator to validate them. I was (falsely?) under the impression that Jboss provides such a validator.
Turns out I missed that you need another annotation to enforce the checks:
#POST
#Path("test/")
#Consumes(MediaType.APPLICATION_JSON)
public String consumeJson(#Valid TestClass c)
{
return c.toString();
}
That's all that's needed for JBoss to actually enforce the checks.
It should be noted that JBoss doesn't like the use of #Valid and generic classes (like List<PathSegment>) in the same parameter list.
Modern jackson versions have #JsonCreator & #JsonProperty to do some validation during deserialization, e.g. for you case:
public class TestClass
{
#JsonCreator
TestClass(
#JsonProperty(value = "first", required = true) Integer first,
#JsonProperty(value = "second", required = true) Integer second) {
this.first = first;
this.second= second;
}
public String first;
public String second;
public String toString()
{
return (first + ", " + second);
}
}
More robust solution would be to use Bean Validation
Related
NOTE: This is unlike other questions on StackOverflow because they resolve this issue by mapping the two classes manually. Since ScheduleSource and ScheduleTarget are exactly the same classes, I want them to be mapped automatically.
Hi,
I have 2 classes ScheduleSource and ScheduleTarget. They have exactly the same properties.
When I try to use MapStruct to map from ScheduleSource to ScheduleTarget, I get the error:
Can't map property "java.util.Optional<java.time.LocalDate> startDate" to "java.time.LocalDate startDate". Consider to declare/implement a mapping method: "java.time.LocalDate map(java.util.Optional<java.time.LocalDate> value)
I have attached the two files. Can you please help?
Files are:
ScheduleSource, ScheduleTarget - the two Java Beans
ScheduleMapper - the mapping class.
ScheduleMapper.java
package testStructMap;
import org.mapstruct.*;
import org.mapstruct.factory.*;
#Mapper
public interface ScheduleMapper {
ScheduleMapper INSTANCE = Mappers.getMapper( ScheduleMapper.class );
ScheduleTarget scheduleSourceToScheduleTarget(ScheduleSource scheduleSource);
}
ScheduleSource.java, ScheduleTarget.java - same structure
package testStructMap;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Optional;
import javax.validation.constraints.*;
public class ScheduleSource {
#FutureOrPresent
#NotNull
private LocalDate startDate;
#NotBlank
private String repeatType;
#Positive
private Integer occurrences;
public Optional<LocalDate> getStartDate() {
return Optional.ofNullable(startDate);
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public String getRepeatType() {
return repeatType;
}
public void setRepeatType(String repeatType) {
this.repeatType = repeatType;
}
public Optional<Integer> getOccurrences() {
return Optional.ofNullable(occurrences);
}
public void setOccurrences(Integer occurrences) {
this.occurrences = occurrences;
}
}
In 1.3.0.beta1 the following is supported:
package testStructMap;
import org.mapstruct.*;
import org.mapstruct.factory.*;
#Mapper
public interface ScheduleMapper {
ScheduleMapper INSTANCE = Mappers.getMapper( ScheduleMapper.class );
ScheduleTarget scheduleSourceToScheduleTarget(ScheduleSource scheduleSource);
default <T> T unwrapOptional(Optional<T> optional) {
return optional.orElse(null);
}
}
I'm not familiar with mapstruct, but I can guess it maps different objects :)
If your source and target classes have the same structure then the problem is
public Optional<LocalDate> getStartDate();
public void setStartDate(LocalDate startDate);
So it gets the Optional object and tries to pass it to a method accepting a LocalDate.
So your possible ways of action are
change getter to return a simple object
change setter to accept an optional (which is fine I guess, but
seems a bit off)
declare a mapper method
Map target Optional to source which is not Optional how can we make it follow the example below:
#Named("HelperClass")
class Helper {
#Named("convertToOptional")
public Optional<KontaktpersonVerwandtschaftsgradTyp> convertToOptional(KontaktpersonVerwandtschaftsgradTyp optional) {
return optional != null ? Optional.of(optional) : Optional.empty();
};
}
#Mapping(target = "kontaktpersonVerwandtschaftsgrad", source = "tdfFall.kontaktpersonVerwandtschaftsgrad", qualifiedByName = { "HelperClass",
"convertToOptional" })
and also we need to add uses and the name of the Helper class
#Mapper(componentModel = "spring", uses = { Helper.class })
With the following Java code:
public class Bean{
private String value;
public Bean(#NonNull String value) {
//Usually fail-fast validation can be added here if it is needed
this.value = value;
}
public String getValue() {return this.value;}
}
Is it possible to check the constructor argument value by means of the annotation, #NonNull at run time other than compile time? Personally I still did not find any checker-framework, which can do validation checking at run time. However, is it possible to implement an Annotation processor to do run time checking?
You should take a look at #NotNull from javax.validation.constraints.
I use it in my models and it throw a Constraint exception when I try to save a model with a null #NotNull value.
The import is import javax.validation.constraints.NotNull;
If you are using Spring and mongodb, you'll have to configure it so it works, I have found a piece of code somewhere on the Internet (can't remember where), you may use it:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
public class CustomRepositoryRestConfigurerAdapter {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener(
#Qualifier("localValidatorFactoryBean") LocalValidatorFactoryBean lfb
) {
return new ValidatingMongoEventListener(lfb);
}
}
Yes. Lombok's #NonNull is a runtime check which just inserts an if-statement with a throw:
With Lombok
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
Vanilla Java
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
if (person == null) {
throw new NullPointerException("person is marked #NonNull but is null");
}
this.name = person.getName();
}
}
Misconception at your end: there is no single answer to your question.
Some annotations, when used on source code like this are mainly targeting compile time. Like some static analysis tool that analyses the data flow to tell you that you are violating "annotated" contracts here or there.
But some annotations are also "meant" to be used at runtime, for example to be used with "beans". Such objects might come in as parameter of a HTTP request, and then you have some framework checking if the content received as JSON for example is actually valid, according to the rules specified via annotations. See this tutorial for some examples.
I have a data class, something like this:
public class Person {
private String name;
private Long code;
// corresponding getters and setters
}
I want to write two web services that provide two different JSON representation of Person. For example, one of them provide {"name":"foo"} but the other one {"name":"foo", "code":"123"}.
As a more complicated scenario, suppose that Person has a reference property, for example address. Address has its own properties as well and I expect that both of my web services consider this property but each of which do this in their own manner.
How should my SpringMVC views be like?
Please note that I'm new to SpringMVC. So give me a sample code beside your answer, please.
UPDATE 1: After few days, all answers push me to solve the problem in controllers or by annotating the data classes. But I want to do this in views, without any more java codes. Can I do it in JSP files or thymeleaf templates or even in .properties files?
UPDATE 2: I found json-taglib. But somehow it is left out of new upgrades. Is there any similar solution?
You're using Spring-MVC so Jackson is in charge of JSON serialize and deserializing.
In this case, you can use #JsonInclude(Include.NON_NULL) to ignore null field during serialization.
public class Person {
#JsonInclude(Include.NON_NULL)
private String name;
#JsonInclude(Include.NON_NULL)
private Long code;
// corresponding getters and setters
}
If your name or code is null then it is excluded from output JSON
So if you pass code as null, your ouput JSON will look like {"name":"foo"}
When creating JSon with Spring MVC the "view renderer", by default, is Jackson. There is no need to use things like JSP or other view technology. What you want to do, is to tell Jackson how to render an object for a given situation. Multiple options are available, but I would suggest to use projections.
An example:
#RestController
#RequestMapping(value = "person")
public class PersonController {
private final ProjectionFactory projectionFactory;
public PersonController(ProjectionFactory projectionFactory) {
this.projectionFactory = projectionFactory;
}
#GetMapping("...")
public PersonBase getPerson(..., #RequestParam(value = "view", required = false, defaultValue = "base") String view) {
...
if(view.equals("extended")) {
return projectionFactory.createProjection(PersonExtended.class, person);
} else {
return projectionFactory.createProjection(PersonBase.class, person);
}
}
}
public interface PersonBase {
String getName();
}
public interface PersonExtended extends PersonBase {
Long getCode;
}
The view layer of your application are the projection classes (put then in one package, the view package if you wish).
A Controller can choose what view to render, or you could make the result dynamic as in the example.
Your question on how to render the address could be solved with another projection like this:
public interface PersonWithAddress extends PersonExtended {
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreetName();
String getZipcode();
...
}
You can look for dynamic filtering of fields using MappingJacksonValue.
The filter allows you to serialize fields that meet custom criteria.
You can check my sample code here:
package com.github.tddiaz.jsonresponsefiltering;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import jdk.nashorn.internal.objects.annotations.Getter;
import lombok.Data;
import lombok.NonNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#RestController
class Controller {
#GetMapping("/filter")
public ResponseEntity filter() {
final Response response = new Response("value1", "value2", "value3");
//ignore field3; will only return values of field1 and field2
final SimpleBeanPropertyFilter beanPropertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
final FilterProvider filterProvider = new SimpleFilterProvider().addFilter("responseFilter", beanPropertyFilter);
final MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(response);
mappingJacksonValue.setFilters(filterProvider);
return ResponseEntity.ok(mappingJacksonValue);
}
}
#JsonFilter("responseFilter")
#Data
class Response {
#NonNull
private String field1, field2, field3;
}
}
Use Projection for expose in diferent manners your json, and ResourceProcessor if you need to add more info to this model through projection, e.g another DB table perhaps.
Based on your use case, just call the controller of your choice from the jsp/js page ...For e.g. Let's say Admin is the user then call AdminController else call User Controller ...this can be done using a simple if/else condition...you can also look into Proxy Design Pattern but that depends on the use case
I recommend you to use JSON.stringify(value[, replacer[, space]]) function on frontend. I have given an example below. You have a write a custom function according to your requirements on the specific view.
Eg. The below example ignores null values. Here I have written editperson function which removes null values.
The function has two input parameters i.e. key and value. Write your logic according to the keys and values which you want to remove or change.
var springperson = { "name":"foo","code":null }
console.log(springperson); // person recieved from spring
function editjson(key, value){
if (value !== null) return value
}
var editperson = JSON.stringify(springperson, editjson); // String representation of person
var personjson=JSON.parse(editperson); // JSON object representation of person
console.log(personjson); // person as required by the view
Comment if you have any issues.
I have a RESTFul API consuming/returning JSON in the request/response body. When the client sends invalid data (valid JSON but invalid values for the fields) I want to be able to return a JSON structure (as well as the relevant 400+ code).
This structure would then allow the frontend to parse the errors on a per-field basis and render the errors alongside the input fields.
E.g. ideal output:
{
"errors":{
"name":["invalid chars","too long","etc"]
"otherfield":["etc"]
}
}
I am using Resteasy for the API, and using violation exceptions it's fairly easy to get it to render JSON errors:
#Provider
#Component
public class ValidationExceptionHandler implements ExceptionMapper<ResteasyViolationException> {
public Response toResponse(ResteasyViolationException exception) {
Multimap<String,String> errors = ArrayListMultimap.create();
Consumer<ResteasyConstraintViolation> consumer = (violation) -> {
errors.put(violation.getPath(), violation.getMessage());
};
exception.getParameterViolations().forEach(consumer);
Map<String, Map<String, Collection<String>>> top = new HashMap<>();
top.put("errors", errors.asMap());
return Response.status(Status.BAD_REQUEST).entity(top)
.build();
}
}
However, the error paths (violation.getPath()) are property-centric rather than XmlElement-name-centric.
E.g. the above outputs:
{
"errors":{"createCampaign.arg1.name":["invalid chars","etc"]}
}
I have tried stripping the index back from the last dot to get "name" but there are other issues with that hack.
E.g. if my "name" property isn't "name" it doesn't work:
#XmlElement(name="name")
#NotNull
private String somethingelse;
"somethingelse" will be returned to client, but they client has no idea what that is:
{
"errors":{"somethingelse":["cannot be null"]}
}
The client wants "name" since that is what the field was called when they sent it.
My resource:
package com.foo.api;
import org.springframework.stereotype.Service;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.foo.dto.CarDTO;
#Service
#Path("/car")
public class CarResource {
#POST
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public CarDTO create(
#Valid CarDTO car
) {
//do some persistence
return car;
}
}
example dto:
package com.foo.dto;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.xml.bind.annotation.XmlElement;
public class CarDTO {
#Min(1)
#Max(10)
#NotNull
#XmlElement(name="gears")
private int cogs;
}
This article describes quite well what you need to do.
Basically you should implement an ExceptionMapper.
#Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
#Override
public Response toResponse(ValidationException exception) {
Response myResponse;
// build your Response based on the data provided by the exception
return myResponse;
}
}
A custom error message can be used so you wouldnt need to look at the path
#NotNull(message="name cannot be null")
My domain Objects are enhanced using lombok, which generates the java.beans #ConstructorProperties annotation for the constructors of immutable objects.
Now in my frontend artifact, I'd like to serialize these objects to JSON using Jackson 2.
For Jackson 1, this could be done using Jackson Extensions. Is there such a solution for Jackson 2 as well or do I have to write it myself?
My main problem is that I want to keep my domain Objects frontend agnostic, so I wouldn't like to pollute them with Jackson annotations.
And no: Java 8 parameter names is not an option, as I am stuck with Java 7 for the time being.
Sean Patrick Floyd has already written a solution, but I am posting my solution because his is proprietary. This is a Jackson module that uses an AnnotationIntrospector to make a constructor annotated with #ConstructorProperties a jackson #JsonCreator.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
public class ConstructorPropertiesModule extends SimpleModule {
public ConstructorPropertiesModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ConstructorPropertiesAnnotationIntrospector());
}
public static class ConstructorPropertiesAnnotationIntrospector extends NopAnnotationIntrospector {
#Override
public boolean hasCreatorAnnotation(Annotated a) {
if (!(a instanceof AnnotatedConstructor)) {
return false;
}
AnnotatedConstructor ac = (AnnotatedConstructor) a;
Constructor<?> c = ac.getAnnotated();
ConstructorProperties properties = c.getAnnotation(ConstructorProperties.class);
if (properties == null) {
return false;
}
for (int i = 0; i < ac.getParameterCount(); i++) {
final String name = properties.value()[i];
final int index = i;
JsonProperty jsonProperty = new JsonProperty() {
#Override
public String value() {
return name;
}
#Override
public boolean required() {
return false;
}
#Override
public Class<? extends Annotation> annotationType() {
return JsonProperty.class;
}
#Override
public int index() {
return index;
}
};
ac.getParameter(i).addOrOverride(jsonProperty);
}
return true;
}
}
}
The module can then be registered to an object mapper to deserialize JSON using the #ConstructorProperties annotation:
ObjectMapper m = new ObjectMapper();
m.registerModules(new ConstructorPropertiesModule());
As others stated Jackson now supports #ConstructorProperties - unfortunatelly. Because it messed up things.
The logic Jackson applies is quite unfortunate. If multiple #ConstructorProperties annotated constructor are present it will create the object via the one with most parameters. Ops. This is problem especially with Lombok which annotates all constructors with #ConstructorProperties. But anyway, this annotation is not there solely for Jackson. It makes sense to annotate every single constructor for any code inspection tool which may use this information. Lombok is right here.
Imagine following object:
#Data
#Builder
#NoArgsConstructor // for Jackson
#AllArgsConstructor // for builder
public class MyDto {
private Type1 value1 = Type1.NONE;
private Type2 value2;
}
Here Jackson will always use the all-args constructor because it is annotated with #ConstructorProperties and has most parameters.
This also means that if you set only value2 in your JSON object the value1 becomes null. Not what you would expect.
Conclusion: the current behaviour (when used with Lombok or annotate more than one constructor) doesn't allow for the easy class-level default values.
Workaround: #AllArgsConstructor(suppressConstructorProperties=true) - but this is claimed to be deprecated soon as it's present just for java 1.5 compatibility purposes.
This issue has finally been resolved in Jackson 2.7 and #ConstructorProperties are now supported out-of-the-box.
See https://github.com/FasterXML/jackson-databind/issues/905
I'm afraid you will have to write a similar wrapper for Jackson2 yourself.