Initially I had an old xsd and from that I generated classes using xjc. I am trying to use JaxB annotation based validation but it seems Validation is ignoring required. Some of the xml nodes are mandatory but as validator is not using required marked in #XMLElement so object is going to backend system and then it fails.
#XmlElement(name = "", required = true)
#Configuration
public class AppConfig {
#Bean
public JaxbAnnotationModule jaxbAnnotationModule() {
return new JaxbAnnotationModule();
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());
mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(mapper.getTypeFactory()));
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
}
Validation done using
#Autowired
private Validator validator;
...Some code...
Set<ConstraintViolation<Request>> violations = validator.validate(request);
Related
I am trying to force SpringBoot to use Gson instead of Jackson. I've read most of the articles I've found online and I am still seeing Jackson being used. Here's what I've done
Added
spring:
http: { converters: { preferred-json-mapper: gson } }
mvc: { converters: {preferred-json-mapper: gson } }
in application.yaml
Updated POM
Added gson dependency
Added jackson-databind to exclusion list in spring-boot-starter-web depedency.
Added #EnableAutoConfiguration(exclude = JacksonAutoConfiguration.class) to main class.
Written below #Configuration class:
#Configuration
#Slf4j
public class MyConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters (List<HttpMessageConverters<?>> converters) {
log.debug("Setting gson converter");
converters.add(new GsonHttpMessageConverter(myCustomGsonInstance()));
}
public Gson myCustomGsonInstance() {
return new Gson();
}
}
When running tests in debug, I can see that Jackson is still listed in the HttpMessageConverters list and Gson is not.
Update:
This behavior is seen while running live and in the below test class.
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = MOCK)
#ExtendWith(MockitoExtension.class)
public class MyTestClass {
#Autowired
private MyController controller;
private MockMvc mockMvc;
#BeforeEach
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(controller)
// .setMessageConverters(new GsonHttpMessageConverter(myCustomGsonInstance())) // if I add this, the test passes.
.build();
}
#Test
public void happyFlow(){
// given
URI uri = "/test/uri";
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
// when
String responseBody = mockMvc.perform(get(uri).headers(headers)).andReturn().getResponse().getContentAsString();
// then
assertThat(responseBody, wasSerializedByGson());
}
}
It looks like you're using the wrong property for configuring the preferred JSON mapper. You are using spring.http.converters.preferred-json-mapper but the correct property is spring.mvc.converters.preferred-json-mapper. In application.yaml, that would be the following:
spring:
mvc:
converters:
preferred-json-mapper: gson
Spring Boot comes with Gson Auto Configuration support: Source Code
So you have to Autowire the Gson singleton instance to be used by your WebMvcConfigurer in addition to enabling the yaml property:
#Configuration
#Slf4j
public class MyConfig implements WebMvcConfigurer {
#Autowired
private Gson gson;
#Override
public void extendMessageConverters (List<HttpMessageConverters<?>> converters) {
log.debug("Setting gson converter");
converters.add(new GsonHttpMessageConverter(gson));
}
}
And the yaml properties borrowed from Andy Wilkinson:
spring:
mvc:
converters:
preferred-json-mapper: gson
With this setup Spring MVC is using the same Gson instance as the one Autowired in your configuration.
And in your test, it should look like this:
#WebMvcTest(MyController.class)
public class MyTestClass {
#Autowired
private MockMvc mockMvc;
#Autowired
private MyController controller;
#Test
public void happyFlow(){
// given
URI uri = "/test/uri";
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
// when
String responseBody = mockMvc.perform(get(uri).headers(headers)).andReturn().getResponse().getContentAsString();
// then
assertThat(responseBody, wasSerializedByGson());
}
}
We're using JAX-RS (Jersey implementation) to call to external systems.
On JAX-RS Client's creation I'm registering the below context resolver to use custom ObjectMapper:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
}
But I don't want to have the ObjectMapper defined in JacksonObjectMapperProvider. I want JacksonObjectMapperProvider to be able to retrieve it in runtime from somewhere, or have someone set the ObjectMapper on JacksonObjectMapperProvider.
I cannot do something like bellow, because the ObjectMapper is defined on some instance that creating the jax-rs Client. And here I don't have a reference to that instance:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
return someService.getObjectMapper();
}
}
Is there another way to do it?
Is there a way to pass data to JacksonObjectMapperProvider when registering on Client?
The solution is easier then I thought, instead of registering the class:
ClientConfig clConfig = new ClientConfig();
client.register(JacksonObjectMapperProvider.class);
as I did, you can register an instance of the class, and on instance creation pass whatever you want:
ClientConfig clConfig = new ClientConfig();
client.register(new JacksonObjectMapperProvider(objectMapper));
The updated provider:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
private ObjectMapper mapper;
public JacksonObjectMapperProvider(ObjectMapper mapper)
{
this.mapper = mapper;
}
#Override
public ObjectMapper getContext(Class<?> type)
{
return mapper;
}
}
I am trying to use a CrudRepository in association with spring-data-redis and lettuce. Following all the advice I can find I have configured my spring-boot 2.1.8 application with #ReadingConverters and #WritingConverters but when I try to use the repository I am getting "Path to property must not be null or empty."
Doing some debugging, this seems to be caused by org.springframework.data.redis.core.convert.MappingRedisConverter:393
writeInternal(entity.getKeySpace(), "", source, entity.getTypeInformation(), sink);
The second parameter being the path. This ends up at line 747 of MappingRedisConverter running this code:
} else if (targetType.filter(it -> ClassUtils.isAssignable(byte[].class, it)).isPresent()) {
sink.getBucket().put(path, toBytes(value));
}
Ultimately, the put with an empty path ends up in org.springframework.data.redis.core.convert.Bucket:77 and fails the Assert.hasText(path, "Path to property must not be null or empty."); even though the data has been serialized.
Is this a bug with spring-data-redis or have I got to configure something else?
RedicsConfig.java
#Configuration
#EnableConfigurationProperties({RedisProperties.class})
#RequiredArgsConstructor
#EnableRedisRepositories
public class RedisConfiguration {
private final RedisConnectionFactory redisConnectionFactory;
#Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.findAndRegisterModules();
return objectMapper;
}
#Bean
public RedisCustomConversions redisCustomConversions(List<Converter<?,?>> converters) {
return new RedisCustomConversions(converters);
}
}
I've just included one writing converter here but have several reading and writing ones...
#Component
#WritingConverter
#RequiredArgsConstructor
#Slf4j
public class CategoryWritingConverter implements Converter<Category, byte[]> {
private final ObjectMapper objectMapper;
#Setter
private Jackson2JsonRedisSerializer<Category> serializer;
#Override
public byte[] convert(Category category) {
return getSerializer().serialize(category);
}
private Jackson2JsonRedisSerializer<Category> getSerializer() {
if (serializer == null) {
serializer = new Jackson2JsonRedisSerializer<>(Category.class);
serializer.setObjectMapper(objectMapper);
}
return serializer;
}
}
The object to write:
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("category")
#TypeAlias("category")
public class Category {
#Id
#EqualsAndHashCode.Include
private String categoryCode;
private String categoryText;
}
And the repo:
public interface CategoryRepository extends CrudRepository<Category, String> {
Page<Category> findAll(Pageable pageable);
}
Can anybody advise what I have missed or if this is a bug I should raise on spring-data-redis?
I am using LocalDateTime in my model, after including LocalDateTimeDeserializer,
converted the bean field to
#NotNull
#Column(name = "created")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime created;
and included the
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
property in the SpringBoot's application.properties file, the application is finally able to deserialize the JSON and show properly like,
"created": "2018-04-22T21:21:53.025",
But, when I am doing testing, it ignores the WRITE_DATES_AS_TIMESTAMPS flag, I guess and generates an output for the same string date above like,
"created":{"year":2018,"monthValue":4,"month":"APRIL","dayOfMonth":22,"dayOfYear":112,"dayOfWeek":"SUNDAY","hour":21,"minute":23,"second":16,"nano":986000000,"chronology":{"id":"ISO","calendarType":"iso8601"}}
Please, note that including the property in application.properties in test resources folder did not change anything.
My test configuration looks like,
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration
#Category(IntegrationTest.class)
public class ApplicationTests {
....
Do you have any idea on what I am doing wrong?
I had the same issue and the below solution worked for me,
add the below code in your Applicationtests class
protected MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Autowired
public void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter)Arrays.asList(converters).stream()
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
final ObjectMapper objectMapper = new ObjectMapper();
final JavaTimeModule javaTimeModule = new JavaTimeModule();
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
}
If you want to support your own date formats then add the formatters as well,
//the below customisation is required if you need to support different date formats
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeCustomSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeCustomDeserializer());
objectMapper.registerModule(javaTimeModule);
Where the custom classes will looks like,
public class LocalDateTimeCustomSerializer extends LocalDateTimeSerializer {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
LocalDateTimeCustomSerializer() {
this(FORMATTER);
}
public LocalDateTimeCustomSerializer(DateTimeFormatter f) {
super(f);
}
#Override
protected DateTimeFormatter _defaultFormatter() {
return FORMATTER;
}
}
and
public class LocalDateTimeCustomDeserializer extends LocalDateTimeDeserializer {
public LocalDateTimeCustomDeserializer() {
this(DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss"));
}
public LocalDateTimeCustomDeserializer(DateTimeFormatter formatter) {
super(formatter);
}
}
Is it possible to setup Jersey using Jackson for serialization/deserialization using multiple configured ObjectMappers?
What I would like to be able to do is register a "default" Jackson ObjectMapper and then have the ability to register another feature which provides an ObjectMapper with some specialized configuration which under certain circumstance will "override" the "default" ObjectMapper.
For example, this ContextResolver would be for the "default" mapper:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class JacksonMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper mObjectMapper;
public JacksonMapperProvider() {
mObjectMapper = createMapper();
}
protected abstract ObjectMapper createMapper() {
ObjectMapper mapper = createMapper();
return mapper
.setSerializationInclusion(Include.ALWAYS)
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mObjectMapper;
}
}
And this ContextResolver would be to override the "default" mapper:
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class SpecializedMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper mObjectMapper;
public SpecializedMapperProvider() {
mObjectMapper = createMapper();
}
protected abstract ObjectMapper createMapper() {
ObjectMapper mapper = createMapper();
return mapper
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"))
.registerModule(new SpecializedModule1())
.registerModule(new SpecializedModule2());
}
#Override
public ObjectMapper getContext(Class<?> type) {
if(SomeType.isAssignableFrom(type)) {
return mObjectMapper;
}
return null;
}
}
I see in the JacksonJsonProvider code that Jackson supports ObjectMapper provider injection/resolution. However, in practice, what I am seeing is that the "order" of the providers seems random (I'm guessing it's not, but I can't sort out how to control the order). Sometimes the "override" comes before the "default" and everything works, but on the next server startup the order changes.
I have attempted to get this to work in a number of ways including:
Registering the ContextResolver<ObjectMapper> implementations manually (in differing orders)
Registering the ContextResolver<ObjectMapper> implementations via #Provider annotations
Specifying a priority when registering
I am using the following:
Jersey 2.8
Jackson 2.3.3
Perhaps I am taking a completely incorrect approach?
Is there a better way to achieve what I am trying to do?
Maybe I should just define two separate JAX-RS applications and have a single ObjectMapper configuration for each?
You can configure the order of providers, but it would actually be best to use one provider in this situation:
#Provider
public class JacksonMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper defaultMapper;
private final ObjectMapper specializedMapper;
public JacksonMapperProvider() {
defaultMapper = createDefaultMapper();
specializedMapper = createSpecializedMapper();
}
private static ObjectMapper createDefaultMapper() {
return new ObjectMapper()
.setSerializationInclusion(Include.ALWAYS)
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
}
private static ObjectMapper createSpecializedMapper() {
return new ObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"))
.registerModule(new SpecializedModule1())
.registerModule(new SpecializedModule2());
}
#Override
public ObjectMapper getContext(Class<?> type) {
if (SomeType.isAssignableFrom(type)) {
return specializedMapper;
}
else {
return defaultMapper;
}
}
}
The newest way is
new ObjectMapper().configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
to get ALLOW_UNQUOTED_FIELD_NAMES in recent versions of jackson.