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);
}
}
Related
I use
#Value("${cache.host}")
private String redisHost;
#Value("${cache.port}")
private int redisPort;
I want to get timeToLive in #RedishHash from application properties. How can get this config?
#RedisHash(value = "UserModel", timeToLive = 5)
I give manually above however I want to give from application.properties
i'm not sure if you can do that from application.properties, but u can do it by configuring a RedisCacheManager bean with java based configuration like below :
#Bean
public RedisCacheManager RedisCacheManager(RedisConnectionFactory redisConnectionFactory) {
Map<String, RedisCacheConfiguration> cacheConfig = new HashMap<String, RedisCacheConfiguration>();
cacheConfig.put("UserModel", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(5)));
RedisCacheManager rdisCacheManager = new RedisCacheManager(
RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
RedisCacheConfiguration.defaultCacheConfig(), cacheConfig);
return rdisCacheManager;
}
PS : this method should be in a class with #Configuration annotation
You can create a #Component where you are going to take the values from properties
#Component
public class RedisHashCustom {
private static String redisHashValue;
public static String getRedisHashVaue() {
return redisHashValue;
}
#Value("${application.redis.redishash.value}")
public void setRedisHashValue(String newRedisHashValue) {
redisHashValue= newRedisHashValue;
}
}
Then you need to reference as
#RedisHash(value = "#{T(com.redis.model.RedisHashCustom).getRedisHashValue() }")
Why wont Spring Boot test load #Value properties in the service class? I have a service class with this property on it.
#Value("${azure.storage.connection-string}")
private String connectionString;
I am using JUnit4 . When this test runs, the connectionString property is null.
#TestPropertySource(locations="classpath:/application-test.yml")
#Slf4j
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
public class BlobServiceTest {
#Autowired
private ObjectMapper objectMapper;
private BlobService blobServiceSpy;
#Before
public void setup() {
blobServiceSpy = Mockito.spy(new BlobService(objectMapper));
}
#Test
public void testGetExampleBlob() {
String stubFileId = UUID.randomUUID().toString();
PdfBlob pdfBlob = null;
try {
pdfBlob = blobServiceSpy.downloadBlobContent(stubFileId);
} catch (JsonProcessingException e) {
log.error("Test failed.", e);
Assert.fail();
}
Assert.assertNotNull("PDF blob was null.", pdfBlob);
}
}
There is some kind of answer in this question, but it is very unclear, and the answer needs more info.
This worked but it seems like a "hack":
public class BlobServiceTest {
#Value("${azure.storage.containerName}")
private String containerName;
#Value("${azure.storage.connection-string}")
private String connectionString;
#Autowired
private ObjectMapper objectMapper;
private BlobService blobServiceSpy;
#Before
public void setup() {
blobServiceSpy = Mockito.spy(new BlobService(objectMapper));
//TODO not sure why, but had to setup connectionString property this way
blobServiceSpy.containerName = this.containerName;
blobServiceSpy.connectionString = this.connectionString;
}
Why doesn't SpringBootTest wire up those values for me when I create the Spy object?
When you use new to create your BlobService spring doesn't know about it an therefore doesn't inject your properties. Try to #Autowire your blob service and then wrap it in the spy.
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?
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);
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.