Jackson - mapping OffsetDateTime [duplicate] - jackson

I have problems LocalDateTime deserialization in Junit test. I have simple REST API which returns some DTO object. When I call my endpoint there is no problem with response - it is correct. Then I try to write unit test, obtain MvcResult and with use of ObjectMapper convert it to my DTO object. But I still receive:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.time.LocalDateTime` out of START_ARRAY token
at [Source: (String)"{"name":"Test name","firstDate":[2019,3,11,18,34,43,52217600],"secondDate":[2019,3,11,19,34,43,54219000]}"; line: 1, column: 33] (through reference chain: com.mylocaldatetimeexample.MyDto["firstDate"])
I was trying with #JsonFormat and adding compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.9.8' to my build.gradle but I use Spring Boot 2.1.3.RELEASE so it is involved in it. I do not have any idea how to fix it. My simple endpoint and unit test below:
#RestController
#RequestMapping("/api/myexample")
public class MyController {
#GetMapping("{id}")
public ResponseEntity<MyDto> findById(#PathVariable Long id) {
MyDto myDto = new MyDto("Test name", LocalDateTime.now(), LocalDateTime.now().plusHours(1));
return ResponseEntity.ok(myDto);
}
}
MyDto class
public class MyDto {
private String name;
private LocalDateTime firstDate;
private LocalDateTime secondDate;
// constructors, getters, setters
}
Unit test
public class MyControllerTest {
#Test
public void getMethod() throws Exception {
MyController controller = new MyController();
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/myexample/1"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
String json = mvcResult.getResponse().getContentAsString();
MyDto dto = new ObjectMapper().readValue(json, MyDto.class);
assertEquals("name", dto.getName());
}
}

You create new ObjectMapper in test class:
MyDto dto = new ObjectMapper().readValue(json, MyDto.class);
Try to inject ObjectMapper from Spring context or manually register module:
mapper.registerModule(new JavaTimeModule());
See also:
jackson-modules-java8

Related

Access JAX-RS resource annotations from a JsonbSerializer

I have an application running on Payara 4 using a custom GSON JSON adapter. I would like to migrate to Payara 5 (5.191) and start using JSON-B. In our current application we can control the JSON output using annotations on a resource.
For example using #Summarize:
#GET
#Path("summary/{encryptedId}")
#Produces(MediaType.APPLICATION_JSON)
#Summarize
public Address findSummarized(#PathParam("encryptedId") String encryptedId) {
return super.find(encryptedId);
}
it will cause a different GSON configuration to be used in our #Provider:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class GsonProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
public GsonProvider() {
gson = getGson(EntityAdapter.class);
gsonSummary = getGson(EntitySummaryAdapter.class);
}
...
#Override
public void writeTo(T object,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
boolean summarize = contains(annotations, Summarize.class);
try (PrintWriter printWriter = new PrintWriter(entityStream)) {
printWriter.write((summarize ? gsonSummary : gson).toJson(object));
printWriter.flush();
}
}
}
I want to do something similar in the new JSON-B setup. I annotated our entities with #JsonbTypeSerializer(MySerializer.class), so I would like to be able to detect from within the serializer what it should do: either create a full serialized JSON object, or a summary.
What I hoped to do is set a property in the JsonbConfig, like so:
JsonbConfig config = new JsonbConfig()
.setProperty("com.myCompany.jsonb.summarize", true);
and read it in the serializer using #Context (just guessing that this might work here), like so:
#Context
private JsonbConfiguration config;
.. but that's not. Is there any way to access JAX-RS resource annotations from a JsonbSerializer?
You could accomplish a similar goal using two separate Jsonb instances in your JAX-RS provider class like so:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class JsonbProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
private static final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
.withAdapters(new EntityAdapter()));
private static final Jsonb jsonbSummary = JsonbBuilder.create(new JsonbConfig()
.withAdapters(new EntitySummaryAdapter()));
...
#Override
public void writeTo(T object,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
boolean summarize = contains(annotations, Summarize.class);
try (PrintWriter printWriter = new PrintWriter(entityStream)) {
printWriter.write((summarize ? jsonbSummary : jsonb).toJson(object));
printWriter.flush();
}
}
}
In the end I opted to create summaries from within my entities and drop the annotation on my REST resources. It was a bit of work, but I think it has been worth it.
I created a Summarizable interface and added a default method there to create a simple map summary of any entity, based on a extended version of the PropertyVisibilityStrategy we created for the full version of the entities.
public interface Summarizable {
public default Map<String, Object> toSummary() {
SummaryPropertyVisibilityStrategy summaryStrategy = new SummaryPropertyVisibilityStrategy();
Map<String, Object> summary = new LinkedHashMap<>();
ReflectionUtils.getFields(this.getClass())
.stream()
.filter(summaryStrategy::isVisible)
.map(f -> new AbstractMap.SimpleEntry<>(f.getName(), summarize(f)))
.filter(e -> e.getValue() != null)
.forEach(e -> summary.put(e.getKey(), e.getValue()));
return summary;
}
public default Object summarize(final Field field) {
Object value = ReflectionUtils.getValueJsonb(this, field);
return value != null && Stream.of(ManyToOne.class, OneToOne.class).anyMatch(field::isAnnotationPresent)
? value.toString()
: value;
}
}
public static Object getValueJsonb(final Object object, final Field field) {
field.setAccessible(true);
JsonbTypeAdapter adapterAnnotation = field.getAnnotation(JsonbTypeAdapter.class);
try {
Object value = field.get(object);
return adapterAnnotation == null
? value
: adapterAnnotation.value().newInstance().adaptToJson(value);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

How can I mock the service class in my Controller Test in Micronaut using JUnit5?

I am writing a JUnit test case for the controller in my micronaut application. The controller has a GET endpoint which invokes a method in my service class. I am getting a NullPointerException so I am assuming that my service class might not be properly mocked however, I am not sure. I am using #Mock (Mockito) for the service.
Am I using the correct annotation to mock the service layer? I have tried to search on google but it hasn't given me much to look into. Thanks.
#MicronautTest
public class FPlanControllerTest {
private static final String url = "dummy_url";
#Inject
FPlanService fplanService;
#Inject
#Client("/")
RxHttpClient client;
#Test
public void testGetLayout() {
FPlanUrl expectedFPlanUrl = new FPlanUrl(url);
when(fplanService.getLayoutUrl(Mockito.anyString(), Mockito.anyString()))
.thenReturn(expectedFPlanUrl);
FPlanUrl actualFPlanUrl = client.toBlocking()
.retrieve(HttpRequest.GET("/layout/1000545").header("layoutId", "7"), FPlanUrl.class);
assertEquals(expectedFPlanUrl , actualFPlanUrl);
}
#MockBean(FPlanService.class)
FPlanService fplanService() {
return mock(FPlanService.class);
}
}
I received the below error.
java.lang.NullPointerException at com.apartment.controller.FPlanControllerTest.testGetLayout(FPlanControllerTest.java:44)
Use #MockBean (io.micronaut.test.annotation.MockBean).
Docs - https://micronaut-projects.github.io/micronaut-test/latest/guide/#junit5
Simply try to mock as below :-
#MockBean(MyService.class)
MyService myService() {
return mock(MyService.class);
}
Now the service can be injected as:-
#Inject
private MyService myService;
Use inside your test method as:-
#Test
public void myServiceTest() {
when(myService.foo(any())).thenReturn(any());
MutableHttpResponse<FooResponse> response = controller.bar(new
MyRequest());
Assertions.assertNotNull(response);
}
I figured out what went wrong. This was giving a NullPointerException because the HTTP response was expecting a String and not the FPlanUrl object. The correct code is as below:
#Test
public void testGetLayout() {
FPlanUrl expectedFPlanUrl = new FPlanUrl("http://dummyurl.com");
when(fplanService.getLayoutUrl(Mockito.anyString(), Mockito.anyString()))
.thenReturn(expectedFPlanUrl);
Assertions.assertEquals("{\"url\":\"http://dummyurl.com\"}", client.toBlocking().retrieve(HttpRequest.GET("/layout/123").header("layoutId", "7"), String.class);
verify(fplanService).getLayoutUrl("123","7");
}

Cannot deserialize java.time.Instant

I have a RestEasyClient that has to deserialize an object that has a java.time.Instant inside. I tried to register the new JavaTimeModule from jsr310 but still got errors:
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule());
ResteasyClient client = new ResteasyClientBuilder()
.register(mapper)
.build();
ResteasyWebTarget target = client.target(UriBuilder.fromPath(SERVICE_URL + "/api"));
Error:
Can not construct instance of java.time.Instant: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
After modifying Rest Server to properly serialize the Instant class (ex: "fromTime": 1525681860)
New Error:
Can not construct instance of java.time.Instant: no double/Double-argument constructor/factory method to deserialize from Number value (1.52568186E9)
I managed to simulate this:
ObjectMapper deserializer = new ObjectMapper()
.registerModule(new JavaTimeModule());
Instant probe = deserializer.readValue("1525681860", Instant.class);
System.out.println(probe);
If I remove the "registerModule" line, I get the same error.
Therefore, the conclusion is that RestEasyClient not registering the module. I am definitely doing something wrong.
You could define a ContextResolver for ObjectMapper:
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
this.mapper = createObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
private ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}
And then register the resolver in your client instance:
ResteasyClient client = new ResteasyClientBuilder()
.register(ObjectMapperContextResolver.class).build();
Alternatively you could register an instance of JacksonJsonProvider. This class is the basic implementation of JAX-RS abstractions (MessageBodyReader and MessageBodyWriter) needed for binding JSON content to and from Java objects.
You can use the constructor that accepts an ObjectMapper instance.

Replace #Value property within #Configuration during Spring Boot test

Scenario
I've got a Spring Boot application with a #Configuration annotated Spring configuration class which contains some #Value annotated fields. For testing I want to replace these field values with custom test values.
Unfortunately these test values cannot be overridden using a simple properties file, (String) constants or similar, instead I must use some custom written property resolving Java class (e.g. TargetProperties.getProperty("some.username")).
The problem I have is that when I add a custom PropertySource to the ConfigurableEnvironment within my test configuration, it's already too late because this PropertySource will be added after the e.g. RestTemplate has been created.
Question
How can I override #Value annotated fields within a #Configuration class with properties obtained programmatically via custom Java code before anything else gets initialized?
Code
Production Configuration Class
#Configuration
public class SomeConfiguration {
#Value("${some.username}")
private String someUsername;
#Value("${some.password}")
private String somePassword;
#Bean
public RestTemplate someRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(
new BasicAuthorizationInterceptor(someUsername, somePassword));
return restTemplate;
}
}
Test Configuration Class
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class SomeTest {
#SpringBootConfiguration
#Import({MySpringBootApp.class, SomeConfiguration.class})
static class TestConfiguration {
#Autowired
private ConfigurableEnvironment configurableEnvironment;
// This doesn't work:
#Bean
#Lazy(false)
// I also tried a #PostConstruct method
public TargetPropertiesPropertySource targetPropertiesPropertySource() {
TargetPropertiesPropertySource customPropertySource =
new TargetPropertiesPropertySource();
configurableEnvironment.getPropertySources().addFirst(customPropertySource);
return customPropertySource;
}
}
}
You can override properties directly in the #SpringBootTest annotation using the properties parameter:
#SpringBootTest(properties = {"some.username=user", "some.password=pwd"},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
You can use #TestPropertySource
#TestPropertySource(
properties = {
"some.username=validate",
"some.password=false"
}
)
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTest {
//...
}
You can use constructor injection in production cases, which allows it to set the configuration manually:
#Configuration
public class SomeConfiguration {
private final String someUsername;
private final String somePassword;
#Autowired
public SomeConfiguration(#Value("${some.username}") String someUsername,
#Value("${some.password}") String somePassword) {
this.someUsername = someUsername;
this.somePassword = somePassword;
}
...
)
}
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class SomeTest {
private SomeConfiguration config;
#Before
public init() {
config = new SomeConfiguration("foo", "bar");
}
}

Hibernate Validator and Jackson: Using the #JsonProperty value as the ConstraintViolation PropertyPath?

Say I have a simple POJO like below annotated with Jackson 2.1 and Hibernate Validator 4.3.1 annotations:
final public class Person {
#JsonProperty("nm")
#NotNull
final public String name;
public Person(String name) {
this.name = name;
}
}
And I send JSON like such to a web service:
{"name": null}
Hibernate when it reports the ConstraintViolation uses the class member identifier "name" instead of the JsonProperty annotation value. Does anyone know if it is possible to make the Hibernate Validator look at the annotation of the class and use that value instead?
Unfortunately there is no easy way to do it. But here are some insights that can help you:
Parsing constraint violations
From the ConstraintViolationException, you can get a set of ConstraintViolation, that exposes the constraint violation context:
ConstraintViolation#getLeafBean(): If it is a bean constraint, this method returns the bean instance in which the constraint is applied to.
ConstraintViolation#getPropertyPath(): Returns the path to the invalid property.
From the property path, you can get the leaf node:
Path propertyPath = constraintViolation.getPropertyPath();
Optional<Path.Node> leafNodeOptional =
StreamSupport.stream(propertyPath.spliterator(), false).reduce((a, b) -> b);
Then check if the type of the node is PROPERTY and get its name:
String nodeName = null;
if (leafNodeOptional.isPresent()) {
Path.Node leafNode = leafNodeOptional.get();
if (ElementKind.PROPERTY == leafNode.getKind()) {
nodeName = leafNode.getName();
}
}
Introspecting a class with Jackson
To get the available JSON properties from the leaf bean class, you can introspect it with Jackson (see this answer and this answer for further details):
Class<?> beanClass = constraintViolation.getLeafBean().getClass();
JavaType javaType = mapper.getTypeFactory().constructType(beanClass);
BeanDescription introspection = mapper.getSerializationConfig().introspect(javaType);
List<BeanPropertyDefinition> properties = introspection.findProperties();
Then filter the properties by comparing the leaf node name with the Field name from the BeanPropertyDefinition:
Optional<String> jsonProperty = properties.stream()
.filter(property -> nodeName.equals(property.getField().getName()))
.map(BeanPropertyDefinition::getName)
.findFirst();
Using JAX-RS?
With JAX-RS (if you are using it), you can define an ExceptionMapper to handle ConstraintViolationExceptions:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
...
}
}
To use the ObjectMapper in your ExceptionMapper, you could provide a ContextResolver<T> for it:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = createObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
private ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}
Inject the Providers interface in your ExceptionMapper:
#Context
private Providers providers;
Lookup for your ContextResolver<T> and then get the ObjectMapper instance:
ContextResolver<ObjectMapper> resolver =
providers.getContextResolver(ObjectMapper.class, MediaType.WILDCARD_TYPE);
ObjectMapper mapper = resolver.getContext(ObjectMapper.class);
If you are interested in getting #XxxParam names, refer to this answer.
No, that's not possible. Hibernate Validator 5 (Bean Validation 1.1) has the notion of ParameterNameProviders which return the names to reported in case method parameter constraints are violated but there is nothing comparable for property constraints.
I have raised this issue as I am using problem-spring-web module to do the validation, and that doesn't support bean definition names out of box as hibernate. so I have came up with the below logic to override the createViolation of ConstraintViolationAdviceTrait and fetch the JSONProperty field name for the field and create violations again.
public class CustomBeanValidationAdviceTrait implements ValidationAdviceTrait {
private final ObjectMapper objectMapper;
public CustomBeanValidationAdviceTrait(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Violation createViolation(ConstraintViolation violation) {
String propertyName = getPropertyName(violation.getRootBeanClass(), violation.getPropertyPath().toString());
return new Violation(this.formatFieldName(propertyName), violation.getMessage());
}
private String getPropertyName(Class clazz, String defaultName) {
JavaType type = objectMapper.constructType(clazz);
BeanDescription desc = objectMapper.getSerializationConfig().introspect(type);
return desc.findProperties()
.stream()
.filter(prop -> prop.getInternalName().equals(defaultName))
.map(BeanPropertyDefinition::getName)
.findFirst()
.orElse(defaultName);
}